From ce28c3cd88ef86571d7ebf2d93b12bb2f7454436 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Fri, 15 Nov 2024 21:30:56 -0800 Subject: [PATCH] Delete RSParser. --- .../contents.xcworkspacedata | 7 - .../xcshareddata/xcschemes/RSParser.xcscheme | 79 - .../xcschemes/RSParserTests.xcscheme | 54 - RSParser/LICENSE | 21 - RSParser/Package.swift | 40 - RSParser/README.md | 75 - RSParser/Sources/ObjC/FeedParser.h | 24 - RSParser/Sources/ObjC/NSData+RSParser.h | 26 - RSParser/Sources/ObjC/NSData+RSParser.m | 139 - RSParser/Sources/ObjC/NSString+RSParser.h | 26 - RSParser/Sources/ObjC/NSString+RSParser.m | 348 -- RSParser/Sources/ObjC/ParserData.h | 23 - RSParser/Sources/ObjC/ParserData.m | 26 - RSParser/Sources/ObjC/RSAtomParser.h | 18 - RSParser/Sources/ObjC/RSAtomParser.m | 679 --- RSParser/Sources/ObjC/RSDateParser.h | 22 - RSParser/Sources/ObjC/RSDateParser.m | 461 -- RSParser/Sources/ObjC/RSHTMLLinkParser.h | 35 - RSParser/Sources/ObjC/RSHTMLLinkParser.m | 154 - RSParser/Sources/ObjC/RSHTMLMetadata.h | 97 - RSParser/Sources/ObjC/RSHTMLMetadata.m | 483 -- RSParser/Sources/ObjC/RSHTMLMetadataParser.h | 24 - RSParser/Sources/ObjC/RSHTMLMetadataParser.m | 151 - RSParser/Sources/ObjC/RSHTMLTag.h | 33 - RSParser/Sources/ObjC/RSHTMLTag.m | 43 - RSParser/Sources/ObjC/RSOPMLAttributes.h | 36 - RSParser/Sources/ObjC/RSOPMLAttributes.m | 68 - RSParser/Sources/ObjC/RSOPMLDocument.h | 21 - RSParser/Sources/ObjC/RSOPMLDocument.m | 14 - RSParser/Sources/ObjC/RSOPMLError.h | 19 - RSParser/Sources/ObjC/RSOPMLError.m | 22 - RSParser/Sources/ObjC/RSOPMLFeedSpecifier.h | 24 - RSParser/Sources/ObjC/RSOPMLFeedSpecifier.m | 51 - RSParser/Sources/ObjC/RSOPMLItem.h | 30 - RSParser/Sources/ObjC/RSOPMLItem.m | 87 - RSParser/Sources/ObjC/RSOPMLParser.h | 26 - RSParser/Sources/ObjC/RSOPMLParser.m | 310 -- RSParser/Sources/ObjC/RSParsedArticle.h | 37 - RSParser/Sources/ObjC/RSParsedArticle.m | 134 - RSParser/Sources/ObjC/RSParsedAuthor.h | 19 - RSParser/Sources/ObjC/RSParsedAuthor.m | 34 - RSParser/Sources/ObjC/RSParsedEnclosure.h | 22 - RSParser/Sources/ObjC/RSParsedEnclosure.m | 13 - RSParser/Sources/ObjC/RSParsedFeed.h | 23 - RSParser/Sources/ObjC/RSParsedFeed.m | 32 - RSParser/Sources/ObjC/RSParserInternal.h | 24 - RSParser/Sources/ObjC/RSParserInternal.m | 61 - RSParser/Sources/ObjC/RSRSSParser.h | 19 - RSParser/Sources/ObjC/RSRSSParser.m | 523 -- RSParser/Sources/ObjC/RSSAXHTMLParser.h | 55 - RSParser/Sources/ObjC/RSSAXHTMLParser.m | 321 -- RSParser/Sources/ObjC/RSSAXParser.h | 69 - RSParser/Sources/ObjC/RSSAXParser.m | 353 -- RSParser/Sources/ObjC/include/RSParser.h | 56 - RSParser/Sources/Swift/Exports.swift | 9 - RSParser/Sources/Swift/Feeds/FeedParser.swift | 91 - .../Sources/Swift/Feeds/FeedParserError.swift | 29 - RSParser/Sources/Swift/Feeds/FeedType.swift | 64 - .../Swift/Feeds/JSON/JSONFeedParser.swift | 250 - .../Swift/Feeds/JSON/RSSInJSONParser.swift | 184 - .../Swift/Feeds/ParsedAttachment.swift | 36 - .../Sources/Swift/Feeds/ParsedAuthor.swift | 44 - RSParser/Sources/Swift/Feeds/ParsedFeed.swift | 42 - RSParser/Sources/Swift/Feeds/ParsedHub.swift | 15 - RSParser/Sources/Swift/Feeds/ParsedItem.swift | 67 - .../Sources/Swift/Feeds/XML/AtomParser.swift | 32 - .../Feeds/XML/RSParsedFeedTransformer.swift | 80 - .../Sources/Swift/Feeds/XML/RSSParser.swift | 29 - RSParser/Sources/Swift/JSON/JSONTypes.swift | 12 - .../Sources/Swift/JSON/JSONUtilities.swift | 27 - .../Swift/Utilities/String+RSParser.swift | 17 - .../Tests/RSParserTests/AtomParserTests.swift | 107 - .../RSParserTests/EntityDecodingTests.swift | 47 - .../RSParserTests/FeedParserTypeTests.swift | 245 - .../Tests/RSParserTests/HTMLLinkTests.swift | 43 - .../RSParserTests/HTMLMetadataTests.swift | 154 - RSParser/Tests/RSParserTests/Info.plist | 22 - .../RSParserTests/JSONFeedParserTests.swift | 124 - RSParser/Tests/RSParserTests/OPMLTests.swift | 79 - .../RSParserTests/RSDateParserTests.swift | 108 - .../RSParserTests/RSSInJSONParserTests.swift | 28 - .../Tests/RSParserTests/RSSParserTests.swift | 188 - .../Tests/RSParserTests/Resources/3960.json | 642 --- .../Tests/RSParserTests/Resources/489.rss | 149 - .../RSParserTests/Resources/4fsodonline.atom | 1 - .../Resources/DaringFireball.atom | 1821 ------- .../Resources/DaringFireball.html | 1341 ----- .../Resources/DaringFireball.json | 584 --- .../Resources/DaringFireball.rss | 2278 -------- .../Tests/RSParserTests/Resources/EMarley.rss | 97 - .../RSParserTests/Resources/KatieFloyd.rss | 352 -- .../Resources/OneFootTsunami.atom | 673 --- .../Resources/ScriptingNews.json | 945 ---- .../Tests/RSParserTests/Resources/Subs.opml | 223 - .../Resources/SubsNoTitleAttributes.opml | 237 - .../Resources/YouTubeTheVolvoRocks.html | 24 - .../RSParserTests/Resources/aktuality.rss | 710 --- .../Resources/allthis-partial.json | 1 - .../RSParserTests/Resources/allthis.atom | 520 -- .../RSParserTests/Resources/allthis.json | 1 - .../Tests/RSParserTests/Resources/atp.rss | 4635 ----------------- .../RSParserTests/Resources/authors.json | 28 - .../Tests/RSParserTests/Resources/bio.rdf | 663 --- .../RSParserTests/Resources/cloudblog.rss | 2 - .../Tests/RSParserTests/Resources/coco.html | 2329 --------- .../Tests/RSParserTests/Resources/curt.json | 1 - .../RSParserTests/Resources/dcrainmaker.xml | 1475 ------ .../RSParserTests/Resources/donthitsave.xml | 61 - .../Resources/expertopinionent.atom | 930 ---- .../Tests/RSParserTests/Resources/furbo.html | 372 -- .../RSParserTests/Resources/inessential.html | 172 - .../RSParserTests/Resources/inessential.json | 156 - .../Tests/RSParserTests/Resources/kc0011.rss | 2 - .../RSParserTests/Resources/livemint.xml | 266 - .../RSParserTests/Resources/macworld.rss | 3123 ----------- .../Tests/RSParserTests/Resources/manton.rss | 228 - .../RSParserTests/Resources/monkeydom.rss | 273 - .../Tests/RSParserTests/Resources/natasha.xml | 1066 ---- .../Tests/RSParserTests/Resources/phpxml.rss | 216 - .../Tests/RSParserTests/Resources/pxlnv.json | 249 - .../Tests/RSParserTests/Resources/rose.json | 1189 ----- .../RSParserTests/Resources/russcox.atom | 182 - .../RSParserTests/Resources/scriptingNews.rss | 443 -- .../RSParserTests/Resources/sixcolors.html | 1102 ---- .../RSParserTests/Resources/theomnishow.rss | 135 - 125 files changed, 37661 deletions(-) delete mode 100644 RSParser/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata delete mode 100644 RSParser/.swiftpm/xcode/xcshareddata/xcschemes/RSParser.xcscheme delete mode 100644 RSParser/.swiftpm/xcode/xcshareddata/xcschemes/RSParserTests.xcscheme delete mode 100644 RSParser/LICENSE delete mode 100644 RSParser/Package.swift delete mode 100644 RSParser/README.md delete mode 100755 RSParser/Sources/ObjC/FeedParser.h delete mode 100644 RSParser/Sources/ObjC/NSData+RSParser.h delete mode 100644 RSParser/Sources/ObjC/NSData+RSParser.m delete mode 100755 RSParser/Sources/ObjC/NSString+RSParser.h delete mode 100755 RSParser/Sources/ObjC/NSString+RSParser.m delete mode 100644 RSParser/Sources/ObjC/ParserData.h delete mode 100644 RSParser/Sources/ObjC/ParserData.m delete mode 100755 RSParser/Sources/ObjC/RSAtomParser.h delete mode 100755 RSParser/Sources/ObjC/RSAtomParser.m delete mode 100755 RSParser/Sources/ObjC/RSDateParser.h delete mode 100755 RSParser/Sources/ObjC/RSDateParser.m delete mode 100755 RSParser/Sources/ObjC/RSHTMLLinkParser.h delete mode 100755 RSParser/Sources/ObjC/RSHTMLLinkParser.m delete mode 100755 RSParser/Sources/ObjC/RSHTMLMetadata.h delete mode 100755 RSParser/Sources/ObjC/RSHTMLMetadata.m delete mode 100755 RSParser/Sources/ObjC/RSHTMLMetadataParser.h delete mode 100755 RSParser/Sources/ObjC/RSHTMLMetadataParser.m delete mode 100644 RSParser/Sources/ObjC/RSHTMLTag.h delete mode 100644 RSParser/Sources/ObjC/RSHTMLTag.m delete mode 100755 RSParser/Sources/ObjC/RSOPMLAttributes.h delete mode 100755 RSParser/Sources/ObjC/RSOPMLAttributes.m delete mode 100755 RSParser/Sources/ObjC/RSOPMLDocument.h delete mode 100755 RSParser/Sources/ObjC/RSOPMLDocument.m delete mode 100755 RSParser/Sources/ObjC/RSOPMLError.h delete mode 100755 RSParser/Sources/ObjC/RSOPMLError.m delete mode 100755 RSParser/Sources/ObjC/RSOPMLFeedSpecifier.h delete mode 100755 RSParser/Sources/ObjC/RSOPMLFeedSpecifier.m delete mode 100755 RSParser/Sources/ObjC/RSOPMLItem.h delete mode 100755 RSParser/Sources/ObjC/RSOPMLItem.m delete mode 100755 RSParser/Sources/ObjC/RSOPMLParser.h delete mode 100755 RSParser/Sources/ObjC/RSOPMLParser.m delete mode 100755 RSParser/Sources/ObjC/RSParsedArticle.h delete mode 100755 RSParser/Sources/ObjC/RSParsedArticle.m delete mode 100644 RSParser/Sources/ObjC/RSParsedAuthor.h delete mode 100644 RSParser/Sources/ObjC/RSParsedAuthor.m delete mode 100644 RSParser/Sources/ObjC/RSParsedEnclosure.h delete mode 100644 RSParser/Sources/ObjC/RSParsedEnclosure.m delete mode 100755 RSParser/Sources/ObjC/RSParsedFeed.h delete mode 100755 RSParser/Sources/ObjC/RSParsedFeed.m delete mode 100755 RSParser/Sources/ObjC/RSParserInternal.h delete mode 100755 RSParser/Sources/ObjC/RSParserInternal.m delete mode 100755 RSParser/Sources/ObjC/RSRSSParser.h delete mode 100755 RSParser/Sources/ObjC/RSRSSParser.m delete mode 100755 RSParser/Sources/ObjC/RSSAXHTMLParser.h delete mode 100755 RSParser/Sources/ObjC/RSSAXHTMLParser.m delete mode 100755 RSParser/Sources/ObjC/RSSAXParser.h delete mode 100755 RSParser/Sources/ObjC/RSSAXParser.m delete mode 100644 RSParser/Sources/ObjC/include/RSParser.h delete mode 100644 RSParser/Sources/Swift/Exports.swift delete mode 100644 RSParser/Sources/Swift/Feeds/FeedParser.swift delete mode 100644 RSParser/Sources/Swift/Feeds/FeedParserError.swift delete mode 100644 RSParser/Sources/Swift/Feeds/FeedType.swift delete mode 100644 RSParser/Sources/Swift/Feeds/JSON/JSONFeedParser.swift delete mode 100644 RSParser/Sources/Swift/Feeds/JSON/RSSInJSONParser.swift delete mode 100644 RSParser/Sources/Swift/Feeds/ParsedAttachment.swift delete mode 100644 RSParser/Sources/Swift/Feeds/ParsedAuthor.swift delete mode 100644 RSParser/Sources/Swift/Feeds/ParsedFeed.swift delete mode 100644 RSParser/Sources/Swift/Feeds/ParsedHub.swift delete mode 100644 RSParser/Sources/Swift/Feeds/ParsedItem.swift delete mode 100644 RSParser/Sources/Swift/Feeds/XML/AtomParser.swift delete mode 100644 RSParser/Sources/Swift/Feeds/XML/RSParsedFeedTransformer.swift delete mode 100644 RSParser/Sources/Swift/Feeds/XML/RSSParser.swift delete mode 100644 RSParser/Sources/Swift/JSON/JSONTypes.swift delete mode 100644 RSParser/Sources/Swift/JSON/JSONUtilities.swift delete mode 100644 RSParser/Sources/Swift/Utilities/String+RSParser.swift delete mode 100644 RSParser/Tests/RSParserTests/AtomParserTests.swift delete mode 100644 RSParser/Tests/RSParserTests/EntityDecodingTests.swift delete mode 100644 RSParser/Tests/RSParserTests/FeedParserTypeTests.swift delete mode 100644 RSParser/Tests/RSParserTests/HTMLLinkTests.swift delete mode 100644 RSParser/Tests/RSParserTests/HTMLMetadataTests.swift delete mode 100644 RSParser/Tests/RSParserTests/Info.plist delete mode 100644 RSParser/Tests/RSParserTests/JSONFeedParserTests.swift delete mode 100644 RSParser/Tests/RSParserTests/OPMLTests.swift delete mode 100644 RSParser/Tests/RSParserTests/RSDateParserTests.swift delete mode 100644 RSParser/Tests/RSParserTests/RSSInJSONParserTests.swift delete mode 100644 RSParser/Tests/RSParserTests/RSSParserTests.swift delete mode 100644 RSParser/Tests/RSParserTests/Resources/3960.json delete mode 100644 RSParser/Tests/RSParserTests/Resources/489.rss delete mode 100644 RSParser/Tests/RSParserTests/Resources/4fsodonline.atom delete mode 100644 RSParser/Tests/RSParserTests/Resources/DaringFireball.atom delete mode 100755 RSParser/Tests/RSParserTests/Resources/DaringFireball.html delete mode 100644 RSParser/Tests/RSParserTests/Resources/DaringFireball.json delete mode 100755 RSParser/Tests/RSParserTests/Resources/DaringFireball.rss delete mode 100755 RSParser/Tests/RSParserTests/Resources/EMarley.rss delete mode 100755 RSParser/Tests/RSParserTests/Resources/KatieFloyd.rss delete mode 100755 RSParser/Tests/RSParserTests/Resources/OneFootTsunami.atom delete mode 100644 RSParser/Tests/RSParserTests/Resources/ScriptingNews.json delete mode 100755 RSParser/Tests/RSParserTests/Resources/Subs.opml delete mode 100755 RSParser/Tests/RSParserTests/Resources/SubsNoTitleAttributes.opml delete mode 100644 RSParser/Tests/RSParserTests/Resources/YouTubeTheVolvoRocks.html delete mode 100644 RSParser/Tests/RSParserTests/Resources/aktuality.rss delete mode 100644 RSParser/Tests/RSParserTests/Resources/allthis-partial.json delete mode 100644 RSParser/Tests/RSParserTests/Resources/allthis.atom delete mode 100644 RSParser/Tests/RSParserTests/Resources/allthis.json delete mode 100644 RSParser/Tests/RSParserTests/Resources/atp.rss delete mode 100644 RSParser/Tests/RSParserTests/Resources/authors.json delete mode 100644 RSParser/Tests/RSParserTests/Resources/bio.rdf delete mode 100644 RSParser/Tests/RSParserTests/Resources/cloudblog.rss delete mode 100644 RSParser/Tests/RSParserTests/Resources/coco.html delete mode 100644 RSParser/Tests/RSParserTests/Resources/curt.json delete mode 100644 RSParser/Tests/RSParserTests/Resources/dcrainmaker.xml delete mode 100644 RSParser/Tests/RSParserTests/Resources/donthitsave.xml delete mode 100644 RSParser/Tests/RSParserTests/Resources/expertopinionent.atom delete mode 100755 RSParser/Tests/RSParserTests/Resources/furbo.html delete mode 100755 RSParser/Tests/RSParserTests/Resources/inessential.html delete mode 100644 RSParser/Tests/RSParserTests/Resources/inessential.json delete mode 100644 RSParser/Tests/RSParserTests/Resources/kc0011.rss delete mode 100644 RSParser/Tests/RSParserTests/Resources/livemint.xml delete mode 100644 RSParser/Tests/RSParserTests/Resources/macworld.rss delete mode 100755 RSParser/Tests/RSParserTests/Resources/manton.rss delete mode 100644 RSParser/Tests/RSParserTests/Resources/monkeydom.rss delete mode 100644 RSParser/Tests/RSParserTests/Resources/natasha.xml delete mode 100644 RSParser/Tests/RSParserTests/Resources/phpxml.rss delete mode 100644 RSParser/Tests/RSParserTests/Resources/pxlnv.json delete mode 100644 RSParser/Tests/RSParserTests/Resources/rose.json delete mode 100644 RSParser/Tests/RSParserTests/Resources/russcox.atom delete mode 100644 RSParser/Tests/RSParserTests/Resources/scriptingNews.rss delete mode 100755 RSParser/Tests/RSParserTests/Resources/sixcolors.html delete mode 100644 RSParser/Tests/RSParserTests/Resources/theomnishow.rss diff --git a/RSParser/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/RSParser/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a62..000000000 --- a/RSParser/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/RSParser/.swiftpm/xcode/xcshareddata/xcschemes/RSParser.xcscheme b/RSParser/.swiftpm/xcode/xcshareddata/xcschemes/RSParser.xcscheme deleted file mode 100644 index e6ad3f0f0..000000000 --- a/RSParser/.swiftpm/xcode/xcshareddata/xcschemes/RSParser.xcscheme +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/RSParser/.swiftpm/xcode/xcshareddata/xcschemes/RSParserTests.xcscheme b/RSParser/.swiftpm/xcode/xcshareddata/xcschemes/RSParserTests.xcscheme deleted file mode 100644 index d49f2c2c1..000000000 --- a/RSParser/.swiftpm/xcode/xcshareddata/xcschemes/RSParserTests.xcscheme +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/RSParser/LICENSE b/RSParser/LICENSE deleted file mode 100644 index df7407c86..000000000 --- a/RSParser/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2017 Brent Simmons - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/RSParser/Package.swift b/RSParser/Package.swift deleted file mode 100644 index ecc66e102..000000000 --- a/RSParser/Package.swift +++ /dev/null @@ -1,40 +0,0 @@ -// swift-tools-version:5.10 - -import PackageDescription - -let package = Package( - name: "RSParser", - platforms: [.macOS(.v14), .iOS(.v17)], - products: [ - .library( - name: "RSParser", - type: .dynamic, - targets: ["RSParser"]), - .library( - name: "RSParserObjC", - type: .dynamic, - targets: ["RSParserObjC"]), - ], - dependencies: [ - ], - targets: [ - .target( - name: "RSParser", - dependencies: ["RSParserObjC"], - path: "Sources/Swift", - swiftSettings: [.unsafeFlags(["-warnings-as-errors"])] - ), - .target( - name: "RSParserObjC", - dependencies: [], - path: "Sources/ObjC", - cSettings: [ - .headerSearchPath("include") - ]), - .testTarget( - name: "RSParserTests", - dependencies: ["RSParser"], - exclude: ["Info.plist"], - resources: [.copy("Resources")]), - ] -) diff --git a/RSParser/README.md b/RSParser/README.md deleted file mode 100644 index 435602141..000000000 --- a/RSParser/README.md +++ /dev/null @@ -1,75 +0,0 @@ -# RSParser - -This framework was developed for [NetNewsWire](https://github.com/brentsimmons/NetNewsWire) and is made available here for developers who just need the parsing code. It has no dependencies that aren’t provided by the system. - -_Update 6 Feb. 2018_: RSParser is now a CocoaPod, with the much-appreciated help of [Silver Fox](https://github.com/dcilia). (We _think_ it worked, anyway. Looked like it did.) - -## What’s inside - -This framework includes parsers for: - -* [RSS](http://cyber.harvard.edu/rss/rss.html), [Atom](https://tools.ietf.org/html/rfc4287), [JSON Feed](https://jsonfeed.org/), and [RSS-in-JSON](https://github.com/scripting/Scripting-News/blob/master/rss-in-json/README.md) -* [OPML](http://dev.opml.org/) -* Internet dates -* HTML metadata and links -* HTML entities - -It also includes Objective-C wrappers for libXML2’s XML SAX and HTML SAX parsers. You can write your own parsers on top of these. - -This framework builds for macOS. It *could* be made to build for iOS also, but I haven’t gotten around to it yet. - -## How to parse feeds - -To get the type of a feed, even with partial data, call `FeedParser.feedType(parserData)`, which will return a `FeedType`. - -To parse a feed, call `FeedParser.parse(parserData)`, which will return a [ParsedFeed](Feeds/ParsedFeed.swift). Also see related structs: `ParsedAuthor`, `ParsedItem`, `ParsedAttachment`, and `ParsedHub`. - -You do *not* need to know the type of feed when calling `FeedParser.parse` — it will figure it out and use the correct concrete parser. - -However, if you do want to use a concrete parser directly, see [RSSInJSONParser](Feeds/JSON/RSSInJSONParser.swift), [JSONFeedParser](Feeds/JSON/JSONFeedParser.swift), [RSSParser](Feeds/XML/RSSParser.swift), and [AtomParser](Feeds/XML/AtomParser.swift). - -(Note: if you want to write a feed reader app, please do! You have my blessing and encouragement. Let me know when it’s shipping so I can check it out.) - -## How to parse OPML - -Call `+[RSOPMLParser parseOPMLWithParserData:error:]`, which returns an `RSOPMLDocument`. See related objects: `RSOPMLItem`, `RSOPMLAttributes`, `RSOPMLFeedSpecifier`, and `RSOPMLError`. - -## How to parse dates - -Call `RSDateWithString` or `RSDateWithBytes` (see `RSDateParser`). These handle the common internet date formats. You don’t need to know which format. - -## How to parse HTML - -To get an array of ` - -+ (BOOL)canParseFeed:(RSXMLData * _Nonnull)xmlData; - -- (nonnull instancetype)initWithXMLData:(RSXMLData * _Nonnull)xmlData; - -- (nullable RSParsedFeed *)parseFeed:(NSError * _Nullable * _Nullable)error; - - -@end diff --git a/RSParser/Sources/ObjC/NSData+RSParser.h b/RSParser/Sources/ObjC/NSData+RSParser.h deleted file mode 100644 index be2d892f8..000000000 --- a/RSParser/Sources/ObjC/NSData+RSParser.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// NSData+RSParser.h -// RSParser -// -// Created by Brent Simmons on 6/24/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -@import Foundation; - - -@interface NSData (RSParser) - -- (BOOL)isProbablyHTML; -- (BOOL)isProbablyXML; -- (BOOL)isProbablyJSON; - -- (BOOL)isProbablyJSONFeed; -- (BOOL)isProbablyRSSInJSON; -- (BOOL)isProbablyRSS; -- (BOOL)isProbablyAtom; - -@end - - - diff --git a/RSParser/Sources/ObjC/NSData+RSParser.m b/RSParser/Sources/ObjC/NSData+RSParser.m deleted file mode 100644 index 8ac9aa167..000000000 --- a/RSParser/Sources/ObjC/NSData+RSParser.m +++ /dev/null @@ -1,139 +0,0 @@ -// -// NSData+RSParser.m -// RSParser -// -// Created by Brent Simmons on 6/24/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -#import "NSData+RSParser.h" - - - - -/* TODO: find real-world cases where the isProbably* cases fail when they should succeed, and add them to tests.*/ - -static BOOL bytesAreProbablyHTML(const char *bytes, NSUInteger numberOfBytes); -static BOOL bytesAreProbablyXML(const char *bytes, NSUInteger numberOfBytes); -static BOOL bytesStartWithStringIgnoringWhitespace(const char *string, const char *bytes, NSUInteger numberOfBytes); -static BOOL didFindString(const char *string, const char *bytes, NSUInteger numberOfBytes); -static BOOL bytesStartWithRSS(const char *bytes, NSUInteger numberOfBytes); -static BOOL bytesStartWithRDF(const char *bytes, NSUInteger numberOfBytes); -static BOOL bytesStartWithAtom(const char *bytes, NSUInteger numberOfBytes); - -@implementation NSData (RSParser) - -- (BOOL)isProbablyHTML { - - return bytesAreProbablyHTML(self.bytes, self.length); -} - -- (BOOL)isProbablyXML { - - return bytesAreProbablyXML(self.bytes, self.length); -} - -- (BOOL)isProbablyJSON { - - return bytesStartWithStringIgnoringWhitespace("{", self.bytes, self.length); -} - -- (BOOL)isProbablyJSONFeed { - - if (![self isProbablyJSON]) { - return NO; - } - return didFindString("://jsonfeed.org/version/", self.bytes, self.length) || didFindString(":\\/\\/jsonfeed.org\\/version\\/", self.bytes, self.length); -} - -- (BOOL)isProbablyRSSInJSON { - - if (![self isProbablyJSON]) { - return NO; - } - const char *bytes = self.bytes; - NSUInteger length = self.length; - return didFindString("rss", bytes, length) && didFindString("channel", bytes, length) && didFindString("item", bytes, length); -} - -- (BOOL)isProbablyRSS { - - if (didFindString(" tag, but it should be parsed anyway. It does have some other distinct RSS markers we can find. - return (didFindString("", self.bytes, self.length) && didFindString("", self.bytes, self.length)); -} - -- (BOOL)isProbablyAtom { - - return didFindString(", and & entity-encoded. -@property (readonly, copy) NSString *rsparser_stringByEncodingRequiredEntities; - -- (NSString *)rsparser_md5Hash; - -- (BOOL)rsparser_contains:(NSString *)s; - -@end - -NS_ASSUME_NONNULL_END diff --git a/RSParser/Sources/ObjC/NSString+RSParser.m b/RSParser/Sources/ObjC/NSString+RSParser.m deleted file mode 100755 index 8a4e7d114..000000000 --- a/RSParser/Sources/ObjC/NSString+RSParser.m +++ /dev/null @@ -1,348 +0,0 @@ -// -// NSString+RSParser.m -// RSParser -// -// Created by Brent Simmons on 9/25/15. -// Copyright © 2015 Ranchero Software, LLC. All rights reserved. -// - -#import "NSString+RSParser.h" -#import - - - - -@interface NSScanner (RSParser) - -- (BOOL)rs_scanEntityValue:(NSString * _Nullable * _Nullable)decodedEntity; - -@end - - -@implementation NSString (RSParser) - -- (BOOL)rsparser_contains:(NSString *)s { - - return [self rangeOfString:s].location != NSNotFound; -} - -- (NSString *)rsparser_stringByDecodingHTMLEntities { - - @autoreleasepool { - - NSScanner *scanner = [[NSScanner alloc] initWithString:self]; - scanner.charactersToBeSkipped = nil; - NSMutableString *result = [[NSMutableString alloc] init]; - - while (true) { - - NSString *scannedString = nil; - if ([scanner scanUpToString:@"&" intoString:&scannedString]) { - [result appendString:scannedString]; - } - if (scanner.isAtEnd) { - break; - } - NSUInteger savedScanLocation = scanner.scanLocation; - - NSString *decodedEntity = nil; - if ([scanner rs_scanEntityValue:&decodedEntity]) { - [result appendString:decodedEntity]; - } - else { - [result appendString:@"&"]; - scanner.scanLocation = savedScanLocation + 1; - } - - if (scanner.isAtEnd) { - break; - } - } - - if ([self isEqualToString:result]) { - return self; - } - return [result copy]; - } -} - - -static NSDictionary *RSEntitiesDictionary(void); -static NSString *RSParserStringWithValue(uint32_t value); - -- (NSString * _Nullable)rs_stringByDecodingEntity { - - // self may or may not have outer & and ; characters. - - NSMutableString *s = [self mutableCopy]; - - if ([s hasPrefix:@"&"]) { - [s deleteCharactersInRange:NSMakeRange(0, 1)]; - } - if ([s hasSuffix:@";"]) { - [s deleteCharactersInRange:NSMakeRange(s.length - 1, 1)]; - } - - NSDictionary *entitiesDictionary = RSEntitiesDictionary(); - - NSString *decodedEntity = entitiesDictionary[self]; - if (decodedEntity) { - return decodedEntity; - } - - if ([s hasPrefix:@"#x"] || [s hasPrefix:@"#X"]) { // Hex - NSScanner *scanner = [[NSScanner alloc] initWithString:s]; - scanner.charactersToBeSkipped = [NSCharacterSet characterSetWithCharactersInString:@"#xX"]; - unsigned int hexValue = 0; - if ([scanner scanHexInt:&hexValue]) { - return RSParserStringWithValue((uint32_t)hexValue); - } - return nil; - } - - else if ([s hasPrefix:@"#"]) { - [s deleteCharactersInRange:NSMakeRange(0, 1)]; - NSInteger value = s.integerValue; - if (value < 1) { - return nil; - } - return RSParserStringWithValue((uint32_t)value); - } - - return nil; -} - -- (NSString *)rsparser_stringByEncodingRequiredEntities { - NSMutableString *result = [NSMutableString string]; - - for (NSUInteger i = 0; i < self.length; ++i) { - unichar c = [self characterAtIndex:i]; - - switch (c) { - case '<': - [result appendString:@"<"]; - break; - case '>': - [result appendString:@">"]; - break; - case '&': - [result appendString:@"&"]; - break; - default: - [result appendFormat:@"%C", c]; - break; - } - } - - return [result copy]; -} - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -- (NSData *)_rsparser_md5HashData { - - NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding]; - unsigned char hash[CC_MD5_DIGEST_LENGTH]; - CC_MD5(data.bytes, (CC_LONG)data.length, hash); - - return [NSData dataWithBytes:(const void *)hash length:CC_MD5_DIGEST_LENGTH]; -} -#pragma GCC diagnostic pop - -- (NSString *)rsparser_md5Hash { - - NSData *md5Data = [self _rsparser_md5HashData]; - const Byte *bytes = md5Data.bytes; - return [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]]; -} - - -@end - -@implementation NSScanner (RSParser) - -- (BOOL)rs_scanEntityValue:(NSString * _Nullable * _Nullable)decodedEntity { - - NSString *s = self.string; - NSUInteger initialScanLocation = self.scanLocation; - static NSUInteger maxEntityLength = 20; // It’s probably smaller, but this is just for sanity. - - while (true) { - - unichar ch = [s characterAtIndex:self.scanLocation]; - if ([NSCharacterSet.whitespaceAndNewlineCharacterSet characterIsMember:ch]) { - break; - } - if (ch == ';') { - if (!decodedEntity) { - return YES; - } - NSString *rawEntity = [s substringWithRange:NSMakeRange(initialScanLocation + 1, (self.scanLocation - initialScanLocation) - 1)]; - *decodedEntity = [rawEntity rs_stringByDecodingEntity]; - self.scanLocation = self.scanLocation + 1; - return *decodedEntity != nil; - } - - self.scanLocation = self.scanLocation + 1; - if (self.scanLocation - initialScanLocation > maxEntityLength) { - break; - } - if (self.isAtEnd) { - break; - } - } - - return NO; -} - -@end - -static NSString *RSParserStringWithValue(uint32_t value) { - // From WebCore's HTMLEntityParser - static const uint32_t windowsLatin1ExtensionArray[32] = { - 0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, // 80-87 - 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008D, 0x017D, 0x008F, // 88-8F - 0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, // 90-97 - 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x017E, 0x0178 // 98-9F - }; - - if ((value & ~0x1Fu) == 0x80u) { // value >= 128 && value < 160 - value = windowsLatin1ExtensionArray[value - 0x80]; - } - - value = CFSwapInt32HostToLittle(value); - - return [[NSString alloc] initWithBytes:&value length:sizeof(value) encoding:NSUTF32LittleEndianStringEncoding]; -} - -static NSDictionary *RSEntitiesDictionary(void) { - - static NSDictionary *entitiesDictionary = nil; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - - entitiesDictionary = @{ - // Named entities - @"AElig": @"Æ", - @"Aacute": @"Á", - @"Acirc": @"Â", - @"Agrave": @"À", - @"Aring": @"Å", - @"Atilde": @"Ã", - @"Auml": @"Ä", - @"Ccedil": @"Ç", - @"Dstrok": @"Ð", - @"ETH": @"Ð", - @"Eacute": @"É", - @"Ecirc": @"Ê", - @"Egrave": @"È", - @"Euml": @"Ë", - @"Iacute": @"Í", - @"Icirc": @"Î", - @"Igrave": @"Ì", - @"Iuml": @"Ï", - @"Ntilde": @"Ñ", - @"Oacute": @"Ó", - @"Ocirc": @"Ô", - @"Ograve": @"Ò", - @"Oslash": @"Ø", - @"Otilde": @"Õ", - @"Ouml": @"Ö", - @"Pi": @"Π", - @"THORN": @"Þ", - @"Uacute": @"Ú", - @"Ucirc": @"Û", - @"Ugrave": @"Ù", - @"Uuml": @"Ü", - @"Yacute": @"Y", - @"aacute": @"á", - @"acirc": @"â", - @"acute": @"´", - @"aelig": @"æ", - @"agrave": @"à", - @"amp": @"&", - @"apos": @"'", - @"aring": @"å", - @"atilde": @"ã", - @"auml": @"ä", - @"brkbar": @"¦", - @"brvbar": @"¦", - @"ccedil": @"ç", - @"cedil": @"¸", - @"cent": @"¢", - @"copy": @"©", - @"curren": @"¤", - @"deg": @"°", - @"die": @"¨", - @"divide": @"÷", - @"eacute": @"é", - @"ecirc": @"ê", - @"egrave": @"è", - @"eth": @"ð", - @"euml": @"ë", - @"euro": @"€", - @"frac12": @"½", - @"frac14": @"¼", - @"frac34": @"¾", - @"gt": @">", - @"hearts": @"♥", - @"hellip": @"…", - @"iacute": @"í", - @"icirc": @"î", - @"iexcl": @"¡", - @"igrave": @"ì", - @"iquest": @"¿", - @"iuml": @"ï", - @"laquo": @"«", - @"ldquo": @"“", - @"lsquo": @"‘", - @"lt": @"<", - @"macr": @"¯", - @"mdash": @"—", - @"micro": @"µ", - @"middot": @"·", - @"ndash": @"–", - @"not": @"¬", - @"ntilde": @"ñ", - @"oacute": @"ó", - @"ocirc": @"ô", - @"ograve": @"ò", - @"ordf": @"ª", - @"ordm": @"º", - @"oslash": @"ø", - @"otilde": @"õ", - @"ouml": @"ö", - @"para": @"¶", - @"pi": @"π", - @"plusmn": @"±", - @"pound": @"£", - @"quot": @"\"", - @"raquo": @"»", - @"rdquo": @"”", - @"reg": @"®", - @"rsquo": @"’", - @"sect": @"§", - @"shy": RSParserStringWithValue(173), - @"sup1": @"¹", - @"sup2": @"²", - @"sup3": @"³", - @"szlig": @"ß", - @"thorn": @"þ", - @"times": @"×", - @"trade": @"™", - @"uacute": @"ú", - @"ucirc": @"û", - @"ugrave": @"ù", - @"uml": @"¨", - @"uuml": @"ü", - @"yacute": @"y", - @"yen": @"¥", - @"yuml": @"ÿ", - @"infin": @"∞", - @"nbsp": RSParserStringWithValue(160) - }; - }); - - return entitiesDictionary; -} diff --git a/RSParser/Sources/ObjC/ParserData.h b/RSParser/Sources/ObjC/ParserData.h deleted file mode 100644 index 30517d98d..000000000 --- a/RSParser/Sources/ObjC/ParserData.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// ParserData.h -// RSParser -// -// Created by Brent Simmons on 10/4/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -@import Foundation; - -NS_ASSUME_NONNULL_BEGIN - -@interface ParserData : NSObject - -@property (nonatomic, readonly) NSString *url; -@property (nonatomic, readonly) NSData *data; - -- (instancetype)initWithURL:(NSString *)url data:(NSData *)data; - -@end - -NS_ASSUME_NONNULL_END - diff --git a/RSParser/Sources/ObjC/ParserData.m b/RSParser/Sources/ObjC/ParserData.m deleted file mode 100644 index 68c5f0356..000000000 --- a/RSParser/Sources/ObjC/ParserData.m +++ /dev/null @@ -1,26 +0,0 @@ -// -// ParserData.m -// RSParser -// -// Created by Brent Simmons on 10/4/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -#import "ParserData.h" - -@implementation ParserData - -- (instancetype)initWithURL:(NSString *)url data:(NSData *)data { - - self = [super init]; - if (!self) { - return nil; - } - - _url = url; - _data = data; - - return self; -} - -@end diff --git a/RSParser/Sources/ObjC/RSAtomParser.h b/RSParser/Sources/ObjC/RSAtomParser.h deleted file mode 100755 index 27b5d80e4..000000000 --- a/RSParser/Sources/ObjC/RSAtomParser.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// RSAtomParser.h -// RSParser -// -// Created by Brent Simmons on 1/15/15. -// Copyright (c) 2015 Ranchero Software LLC. All rights reserved. -// - -@import Foundation; - -@class ParserData; -@class RSParsedFeed; - -@interface RSAtomParser : NSObject - -+ (RSParsedFeed *)parseFeedWithData:(ParserData *)parserData; - -@end diff --git a/RSParser/Sources/ObjC/RSAtomParser.m b/RSParser/Sources/ObjC/RSAtomParser.m deleted file mode 100755 index eaaeeb638..000000000 --- a/RSParser/Sources/ObjC/RSAtomParser.m +++ /dev/null @@ -1,679 +0,0 @@ -// -// RSAtomParser.m -// RSParser -// -// Created by Brent Simmons on 1/15/15. -// Copyright (c) 2015 Ranchero Software LLC. All rights reserved. -// - - -#import "RSAtomParser.h" -#import "RSSAXParser.h" -#import "RSParsedFeed.h" -#import "RSParsedArticle.h" -#import "NSString+RSParser.h" -#import "RSDateParser.h" -#import "ParserData.h" -#import "RSParsedEnclosure.h" -#import "RSParsedAuthor.h" - -#import - -@interface RSAtomParser () - -@property (nonatomic) NSData *feedData; -@property (nonatomic) NSString *urlString; -@property (nonatomic) BOOL endFeedFound; -@property (nonatomic) BOOL parsingXHTML; -@property (nonatomic) BOOL parsingSource; -@property (nonatomic) BOOL parsingArticle; -@property (nonatomic) BOOL parsingAuthor; -@property (nonatomic) NSMutableArray *attributesStack; -@property (nonatomic, readonly) NSDictionary *currentAttributes; -@property (nonatomic) NSMutableString *xhtmlString; -@property (nonatomic) NSString *link; -@property (nonatomic) NSString *title; -@property (nonatomic) NSMutableArray *articles; -@property (nonatomic) NSDate *dateParsed; -@property (nonatomic) RSSAXParser *parser; -@property (nonatomic, readonly) RSParsedArticle *currentArticle; -@property (nonatomic) RSParsedAuthor *currentAuthor; -@property (nonatomic, readonly) NSDate *currentDate; -@property (nonatomic) NSString *language; - -@end - - -@implementation RSAtomParser - -#pragma mark - Class Methods - -+ (RSParsedFeed *)parseFeedWithData:(ParserData *)parserData { - - RSAtomParser *parser = [[[self class] alloc] initWithParserData:parserData]; - return [parser parseFeed]; -} - - -#pragma mark - Init - -- (instancetype)initWithParserData:(ParserData *)parserData { - - self = [super init]; - if (!self) { - return nil; - } - - _feedData = parserData.data; - _urlString = parserData.url; - _parser = [[RSSAXParser alloc] initWithDelegate:self]; - _attributesStack = [NSMutableArray new]; - _articles = [NSMutableArray new]; - - return self; -} - - -#pragma mark - API - -- (RSParsedFeed *)parseFeed { - - [self parse]; - - RSParsedFeed *parsedFeed = [[RSParsedFeed alloc] initWithURLString:self.urlString title:self.title link:self.link language:self.language articles:self.articles]; - - return parsedFeed; -} - - -#pragma mark - Constants - -static NSString *kTypeKey = @"type"; -static NSString *kXHTMLType = @"xhtml"; -static NSString *kRelKey = @"rel"; -static NSString *kAlternateValue = @"alternate"; -static NSString *kHrefKey = @"href"; -static NSString *kXMLKey = @"xml"; -static NSString *kBaseKey = @"base"; -static NSString *kLangKey = @"lang"; -static NSString *kXMLBaseKey = @"xml:base"; -static NSString *kXMLLangKey = @"xml:lang"; -static NSString *kTextHTMLValue = @"text/html"; -static NSString *kRelatedValue = @"related"; -static NSString *kEnclosureValue = @"enclosure"; -static NSString *kShortURLValue = @"shorturl"; -static NSString *kHTMLValue = @"html"; -static NSString *kEnValue = @"en"; -static NSString *kTextValue = @"text"; -static NSString *kSelfValue = @"self"; -static NSString *kLengthKey = @"length"; -static NSString *kTitleKey = @"title"; - -static const char *kID = "id"; -static const NSInteger kIDLength = 3; - -static const char *kTitle = "title"; -static const NSInteger kTitleLength = 6; - -static const char *kContent = "content"; -static const NSInteger kContentLength = 8; - -static const char *kSummary = "summary"; -static const NSInteger kSummaryLength = 8; - -static const char *kLink = "link"; -static const NSInteger kLinkLength = 5; - -static const char *kPublished = "published"; -static const NSInteger kPublishedLength = 10; - -static const char *kIssued = "issued"; -static const NSInteger kIssuedLength = 7; - -static const char *kUpdated = "updated"; -static const NSInteger kUpdatedLength = 8; - -static const char *kModified = "modified"; -static const NSInteger kModifiedLength = 9; - -static const char *kAuthor = "author"; -static const NSInteger kAuthorLength = 7; - -static const char *kName = "name"; -static const NSInteger kNameLength = 5; - -static const char *kEmail = "email"; -static const NSInteger kEmailLength = 6; - -static const char *kURI = "uri"; -static const NSInteger kURILength = 4; - -static const char *kEntry = "entry"; -static const NSInteger kEntryLength = 6; - -static const char *kSource = "source"; -static const NSInteger kSourceLength = 7; - -static const char *kFeed = "feed"; -static const NSInteger kFeedLength = 5; - -static const char *kType = "type"; -static const NSInteger kTypeLength = 5; - -static const char *kRel = "rel"; -static const NSInteger kRelLength = 4; - -static const char *kAlternate = "alternate"; -static const NSInteger kAlternateLength = 10; - -static const char *kHref = "href"; -static const NSInteger kHrefLength = 5; - -static const char *kXML = "xml"; -static const NSInteger kXMLLength = 4; - -static const char *kBase = "base"; -static const NSInteger kBaseLength = 5; - -static const char *kLang = "lang"; -static const NSInteger kLangLength = 5; - -static const char *kTextHTML = "text/html"; -static const NSInteger kTextHTMLLength = 10; - -static const char *kRelated = "related"; -static const NSInteger kRelatedLength = 8; - -static const char *kShortURL = "shorturl"; -static const NSInteger kShortURLLength = 9; - -static const char *kHTML = "html"; -static const NSInteger kHTMLLength = 5; - -static const char *kEn = "en"; -static const NSInteger kEnLength = 3; - -static const char *kText = "text"; -static const NSInteger kTextLength = 5; - -static const char *kSelf = "self"; -static const NSInteger kSelfLength = 5; - -static const char *kEnclosure = "enclosure"; -static const NSInteger kEnclosureLength = 10; - -static const char *kLength = "length"; -static const NSInteger kLengthLength = 7; - -#pragma mark - Parsing - -- (void)parse { - - self.dateParsed = [NSDate date]; - - @autoreleasepool { - [self.parser parseData:self.feedData]; - [self.parser finishParsing]; - } -} - - -- (void)addArticle { - - RSParsedArticle *article = [[RSParsedArticle alloc] initWithFeedURL:self.urlString]; - article.dateParsed = self.dateParsed; - - [self.articles addObject:article]; -} - - -- (RSParsedArticle *)currentArticle { - - return self.articles.lastObject; -} - - -- (NSDictionary *)currentAttributes { - - return self.attributesStack.lastObject; -} - - -- (NSDate *)currentDate { - - return RSDateWithBytes(self.parser.currentCharacters.bytes, self.parser.currentCharacters.length); -} - - -- (void)addFeedLink { - - if (self.link && self.link.length > 0) { - return; - } - - NSString *related = self.currentAttributes[kRelKey]; - if (related == kAlternateValue) { - self.link = self.currentAttributes[kHrefKey]; - } -} - - -- (void)addFeedTitle { - - if (self.title.length < 1) { - self.title = [self currentString]; - } -} - -- (void)addFeedLanguage { - - if (self.language.length < 0) { - self.language = self.currentAttributes[kXMLLangKey] -; - } -} - -- (void)addLink { - - NSDictionary *attributes = self.currentAttributes; - - NSString *urlString = attributes[kHrefKey]; - if (urlString.length < 1) { - return; - } - - RSParsedArticle *article = self.currentArticle; - - NSString *rel = attributes[kRelKey]; - if (rel.length < 1) { - rel = kAlternateValue; - } - - if (rel == kRelatedValue) { - if (!article.link) { - article.link = urlString; - } - } - else if (rel == kAlternateValue) { - if (!article.permalink) { - article.permalink = urlString; - } - } - else if (rel == kEnclosureValue) { - RSParsedEnclosure *enclosure = [self enclosureWithURLString:urlString attributes:attributes]; - [article addEnclosure:enclosure]; - } -} - -- (RSParsedEnclosure *)enclosureWithURLString:(NSString *)urlString attributes:(NSDictionary *)attributes { - - RSParsedEnclosure *enclosure = [[RSParsedEnclosure alloc] init]; - enclosure.url = urlString; - enclosure.title = attributes[kTitleKey]; - enclosure.mimeType = attributes[kTypeKey]; - enclosure.length = [attributes[kLengthKey] integerValue]; - - return enclosure; -} - -- (void)addContent { - - self.currentArticle.body = [self currentString]; -} - - -- (void)addSummary { - - if (!self.currentArticle.body) { - self.currentArticle.body = [self currentString]; - } -} - - -- (NSString *)currentString { - - return self.parser.currentStringWithTrimmedWhitespace; -} - - -- (void)addArticleElement:(const xmlChar *)localName prefix:(const xmlChar *)prefix { - - if (prefix) { - return; - } - - if (RSSAXEqualTags(localName, kID, kIDLength)) { - self.currentArticle.guid = [self currentString]; - } - - else if (RSSAXEqualTags(localName, kTitle, kTitleLength)) { - self.currentArticle.title = [self currentString]; - } - - else if (RSSAXEqualTags(localName, kContent, kContentLength)) { - [self addContent]; - } - - else if (RSSAXEqualTags(localName, kSummary, kSummaryLength)) { - [self addSummary]; - } - - else if (RSSAXEqualTags(localName, kLink, kLinkLength)) { - [self addLink]; - } - - else if (RSSAXEqualTags(localName, kPublished, kPublishedLength)) { - self.currentArticle.datePublished = self.currentDate; - } - - else if (RSSAXEqualTags(localName, kUpdated, kUpdatedLength)) { - self.currentArticle.dateModified = self.currentDate; - } - - // Atom 0.3 dates - else if (RSSAXEqualTags(localName, kIssued, kIssuedLength)) { - if (!self.currentArticle.datePublished) { - self.currentArticle.datePublished = self.currentDate; - } - } - else if (RSSAXEqualTags(localName, kModified, kModifiedLength)) { - if (!self.currentArticle.dateModified) { - self.currentArticle.dateModified = self.currentDate; - } - } -} - - -- (void)addXHTMLTag:(const xmlChar *)localName { - - if (!localName) { - return; - } - - [self.xhtmlString appendString:@"<"]; - [self.xhtmlString appendString:[NSString stringWithUTF8String:(const char *)localName]]; - - if (self.currentAttributes.count < 1) { - [self.xhtmlString appendString:@">"]; - return; - } - - for (NSString *oneKey in self.currentAttributes) { - - [self.xhtmlString appendString:@" "]; - - NSString *oneValue = self.currentAttributes[oneKey]; - [self.xhtmlString appendString:oneKey]; - - [self.xhtmlString appendString:@"=\""]; - - oneValue = [oneValue stringByReplacingOccurrencesOfString:@"\"" withString:@"""]; - [self.xhtmlString appendString:oneValue]; - - [self.xhtmlString appendString:@"\""]; - } - - [self.xhtmlString appendString:@">"]; -} - - -#pragma mark - RSSAXParserDelegate - -- (void)saxParser:(RSSAXParser *)SAXParser XMLStartElement:(const xmlChar *)localName prefix:(const xmlChar *)prefix uri:(const xmlChar *)uri numberOfNamespaces:(NSInteger)numberOfNamespaces namespaces:(const xmlChar **)namespaces numberOfAttributes:(NSInteger)numberOfAttributes numberDefaulted:(int)numberDefaulted attributes:(const xmlChar **)attributes { - - if (self.endFeedFound) { - return; - } - - NSDictionary *xmlAttributes = [self.parser attributesDictionary:attributes numberOfAttributes:numberOfAttributes]; - if (!xmlAttributes) { - xmlAttributes = [NSDictionary dictionary]; - } - [self.attributesStack addObject:xmlAttributes]; - - if (self.parsingXHTML) { - [self addXHTMLTag:localName]; - return; - } - - if (RSSAXEqualTags(localName, kEntry, kEntryLength)) { - self.parsingArticle = YES; - [self addArticle]; - return; - } - - if (RSSAXEqualTags(localName, kAuthor, kAuthorLength)) { - self.parsingAuthor = YES; - self.currentAuthor = [[RSParsedAuthor alloc] init]; - return; - } - - if (RSSAXEqualTags(localName, kSource, kSourceLength)) { - self.parsingSource = YES; - return; - } - - BOOL isContentTag = RSSAXEqualTags(localName, kContent, kContentLength); - BOOL isSummaryTag = RSSAXEqualTags(localName, kSummary, kSummaryLength); - if (self.parsingArticle && (isContentTag || isSummaryTag)) { - - if (isContentTag) { - self.currentArticle.language = xmlAttributes[kXMLLangKey]; - } - - NSString *contentType = xmlAttributes[kTypeKey]; - if ([contentType isEqualToString:kXHTMLType]) { - self.parsingXHTML = YES; - self.xhtmlString = [NSMutableString stringWithString:@""]; - return; - } - } - - if (!self.parsingArticle && RSSAXEqualTags(localName, kLink, kLinkLength)) { - [self addFeedLink]; - return; - } - - if (RSSAXEqualTags(localName, kFeed, kFeedLength)) { - [self addFeedLanguage]; - } - - [self.parser beginStoringCharacters]; -} - - -- (void)saxParser:(RSSAXParser *)SAXParser XMLEndElement:(const xmlChar *)localName prefix:(const xmlChar *)prefix uri:(const xmlChar *)uri { - - if (RSSAXEqualTags(localName, kFeed, kFeedLength)) { - self.endFeedFound = YES; - return; - } - - if (self.endFeedFound) { - return; - } - - if (self.parsingXHTML) { - - BOOL isContentTag = RSSAXEqualTags(localName, kContent, kContentLength); - BOOL isSummaryTag = RSSAXEqualTags(localName, kSummary, kSummaryLength); - - if (self.parsingArticle && (isContentTag || isSummaryTag)) { - - if (isContentTag) { - self.currentArticle.body = [self.xhtmlString copy]; - } - - else if (isSummaryTag) { - if (self.currentArticle.body.length < 1) { - self.currentArticle.body = [self.xhtmlString copy]; - } - } - } - - if (isContentTag || isSummaryTag) { - self.parsingXHTML = NO; - } - - [self.xhtmlString appendString:@""]; - } - - else if (self.parsingAuthor) { - - if (RSSAXEqualTags(localName, kAuthor, kAuthorLength)) { - self.parsingAuthor = NO; - RSParsedAuthor *author = self.currentAuthor; - if (author.name || author.emailAddress || author.url) { - [self.currentArticle addAuthor:author]; - } - self.currentAuthor = nil; - } - else if (RSSAXEqualTags(localName, kName, kNameLength)) { - self.currentAuthor.name = [self currentString]; - } - else if (RSSAXEqualTags(localName, kEmail, kEmailLength)) { - self.currentAuthor.emailAddress = [self currentString]; - } - else if (RSSAXEqualTags(localName, kURI, kURILength)) { - self.currentAuthor.url = [self currentString]; - } - } - - else if (RSSAXEqualTags(localName, kEntry, kEntryLength)) { - self.parsingArticle = NO; - } - - else if (self.parsingArticle && !self.parsingSource) { - [self addArticleElement:localName prefix:prefix]; - } - - else if (RSSAXEqualTags(localName, kSource, kSourceLength)) { - self.parsingSource = NO; - } - - else if (!self.parsingArticle && !self.parsingSource && RSSAXEqualTags(localName, kTitle, kTitleLength)) { - [self addFeedTitle]; - } - - [self.attributesStack removeLastObject]; -} - - -- (NSString *)saxParser:(RSSAXParser *)SAXParser internedStringForName:(const xmlChar *)name prefix:(const xmlChar *)prefix { - - if (prefix && RSSAXEqualTags(prefix, kXML, kXMLLength)) { - - if (RSSAXEqualTags(name, kBase, kBaseLength)) { - return kXMLBaseKey; - } - if (RSSAXEqualTags(name, kLang, kLangLength)) { - return kXMLLangKey; - } - } - - if (prefix) { - return nil; - } - - if (RSSAXEqualTags(name, kRel, kRelLength)) { - return kRelKey; - } - - if (RSSAXEqualTags(name, kType, kTypeLength)) { - return kTypeKey; - } - - if (RSSAXEqualTags(name, kHref, kHrefLength)) { - return kHrefKey; - } - - if (RSSAXEqualTags(name, kAlternate, kAlternateLength)) { - return kAlternateValue; - } - - if (RSSAXEqualTags(name, kLength, kLengthLength)) { - return kLengthKey; - } - - if (RSSAXEqualTags(name, kTitle, kTitleLength)) { - return kTitleKey; - } - - return nil; -} - - -static BOOL equalBytes(const void *bytes1, const void *bytes2, NSUInteger length) { - - return memcmp(bytes1, bytes2, length) == 0; -} - - -- (NSString *)saxParser:(RSSAXParser *)SAXParser internedStringForValue:(const void *)bytes length:(NSUInteger)length { - - static const NSUInteger alternateLength = kAlternateLength - 1; - static const NSUInteger textHTMLLength = kTextHTMLLength - 1; - static const NSUInteger relatedLength = kRelatedLength - 1; - static const NSUInteger shortURLLength = kShortURLLength - 1; - static const NSUInteger htmlLength = kHTMLLength - 1; - static const NSUInteger enLength = kEnLength - 1; - static const NSUInteger textLength = kTextLength - 1; - static const NSUInteger selfLength = kSelfLength - 1; - static const NSUInteger enclosureLength = kEnclosureLength - 1; - - if (length == alternateLength && equalBytes(bytes, kAlternate, alternateLength)) { - return kAlternateValue; - } - - if (length == enclosureLength && equalBytes(bytes, kEnclosure, enclosureLength)) { - return kEnclosureValue; - } - - if (length == textHTMLLength && equalBytes(bytes, kTextHTML, textHTMLLength)) { - return kTextHTMLValue; - } - - if (length == relatedLength && equalBytes(bytes, kRelated, relatedLength)) { - return kRelatedValue; - } - - if (length == shortURLLength && equalBytes(bytes, kShortURL, shortURLLength)) { - return kShortURLValue; - } - - if (length == htmlLength && equalBytes(bytes, kHTML, htmlLength)) { - return kHTMLValue; - } - - if (length == enLength && equalBytes(bytes, kEn, enLength)) { - return kEnValue; - } - - if (length == textLength && equalBytes(bytes, kText, textLength)) { - return kTextValue; - } - - if (length == selfLength && equalBytes(bytes, kSelf, selfLength)) { - return kSelfValue; - } - - return nil; -} - - -- (void)saxParser:(RSSAXParser *)SAXParser XMLCharactersFound:(const unsigned char *)characters length:(NSUInteger)length { - - if (self.parsingXHTML) { - NSString *s = [[NSString alloc] initWithBytesNoCopy:(void *)characters length:length encoding:NSUTF8StringEncoding freeWhenDone:NO]; - if (s == nil) { - return; - } - // libxml decodes all entities; we need to re-encode certain characters - // (<, >, and &) when inside XHTML text content. - [self.xhtmlString appendString:s.rsparser_stringByEncodingRequiredEntities]; - } -} - -@end diff --git a/RSParser/Sources/ObjC/RSDateParser.h b/RSParser/Sources/ObjC/RSDateParser.h deleted file mode 100755 index 5c3745a32..000000000 --- a/RSParser/Sources/ObjC/RSDateParser.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// RSDateParser.h -// RSParser -// -// Created by Brent Simmons on 3/25/15. -// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved. -// - -@import Foundation; - - -// Common web dates -- RFC 822 and 8601 -- are handled here: the formats you find in JSON and XML feeds. -// These may return nil. They may also return garbage, given bad input. - -NSDate *RSDateWithString(NSString *dateString); - -// If you're using a SAX parser, you have the bytes and don't need to convert to a string first. -// It's faster and uses less memory. -// (Assumes bytes are UTF-8 or ASCII. If you're using the libxml SAX parser, this will work.) - -NSDate *RSDateWithBytes(const char *bytes, NSUInteger numberOfBytes); - diff --git a/RSParser/Sources/ObjC/RSDateParser.m b/RSParser/Sources/ObjC/RSDateParser.m deleted file mode 100755 index cb9c572d3..000000000 --- a/RSParser/Sources/ObjC/RSDateParser.m +++ /dev/null @@ -1,461 +0,0 @@ -// -// RSDateParser.m -// RSParser -// -// Created by Brent Simmons on 3/25/15. -// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved. -// - - -#import "RSDateParser.h" -#import - - -typedef struct { - const char *abbreviation; - const NSInteger offsetHours; - const NSInteger offsetMinutes; -} RSTimeZoneAbbreviationAndOffset; - - -#define kNumberOfTimeZones 96 - -static const RSTimeZoneAbbreviationAndOffset timeZoneTable[kNumberOfTimeZones] = { - {"GMT", 0, 0}, //Most common at top, for performance - {"PDT", -7, 0}, {"PST", -8, 0}, {"EST", -5, 0}, {"EDT", -4, 0}, - {"MDT", -6, 0}, {"MST", -7, 0}, {"CST", -6, 0}, {"CDT", -5, 0}, - {"ACT", -8, 0}, {"AFT", 4, 30}, {"AMT", 4, 0}, {"ART", -3, 0}, - {"AST", 3, 0}, {"AZT", 4, 0}, {"BIT", -12, 0}, {"BDT", 8, 0}, - {"ACST", 9, 30}, {"AEST", 10, 0}, {"AKST", -9, 0}, {"AMST", 5, 0}, - {"AWST", 8, 0}, {"AZOST", -1, 0}, {"BIOT", 6, 0}, {"BRT", -3, 0}, - {"BST", 6, 0}, {"BTT", 6, 0}, {"CAT", 2, 0}, {"CCT", 6, 30}, - {"CET", 1, 0}, {"CEST", 2, 0}, {"CHAST", 12, 45}, {"ChST", 10, 0}, - {"CIST", -8, 0}, {"CKT", -10, 0}, {"CLT", -4, 0}, {"CLST", -3, 0}, - {"COT", -5, 0}, {"COST", -4, 0}, {"CVT", -1, 0}, {"CXT", 7, 0}, - {"EAST", -6, 0}, {"EAT", 3, 0}, {"ECT", -4, 0}, {"EEST", 3, 0}, - {"EET", 2, 0}, {"FJT", 12, 0}, {"FKST", -4, 0}, {"GALT", -6, 0}, - {"GET", 4, 0}, {"GFT", -3, 0}, {"GILT", 7, 0}, {"GIT", -9, 0}, - {"GST", -2, 0}, {"GYT", -4, 0}, {"HAST", -10, 0}, {"HKT", 8, 0}, - {"HMT", 5, 0}, {"IRKT", 8, 0}, {"IRST", 3, 30}, {"IST", 2, 0}, - {"JST", 9, 0}, {"KRAT", 7, 0}, {"KST", 9, 0}, {"LHST", 10, 30}, - {"LINT", 14, 0}, {"MAGT", 11, 0}, {"MIT", -9, 30}, {"MSK", 3, 0}, - {"MUT", 4, 0}, {"NDT", -2, 30}, {"NFT", 11, 30}, {"NPT", 5, 45}, - {"NT", -3, 30}, {"OMST", 6, 0}, {"PETT", 12, 0}, {"PHOT", 13, 0}, - {"PKT", 5, 0}, {"RET", 4, 0}, {"SAMT", 4, 0}, {"SAST", 2, 0}, - {"SBT", 11, 0}, {"SCT", 4, 0}, {"SLT", 5, 30}, {"SST", 8, 0}, - {"TAHT", -10, 0}, {"THA", 7, 0}, {"UYT", -3, 0}, {"UYST", -2, 0}, - {"VET", -4, 30}, {"VLAT", 10, 0}, {"WAT", 1, 0}, {"WET", 0, 0}, - {"WEST", 1, 0}, {"YAKT", 9, 0}, {"YEKT", 5, 0} -}; /*See http://en.wikipedia.org/wiki/List_of_time_zone_abbreviations for list*/ - - - -#pragma mark - Parser - -enum { - RSJanuary = 1, - RSFebruary, - RSMarch, - RSApril, - RSMay, - RSJune, - RSJuly, - RSAugust, - RSSeptember, - RSOctober, - RSNovember, - RSDecember -}; - -static NSInteger nextMonthValue(const char *bytes, NSUInteger numberOfBytes, NSUInteger startingIndex, NSUInteger *finalIndex) { - - /*Months are 1-based -- January is 1, Dec is 12. - Lots of short-circuits here. Not strict. GIGO.*/ - - NSUInteger i;// = startingIndex; - NSUInteger numberOfAlphaCharactersFound = 0; - char monthCharacters[3] = {0, 0, 0}; - - for (i = startingIndex; i < numberOfBytes; i++) { - - *finalIndex = i; - char character = bytes[i]; - - BOOL isAlphaCharacter = (BOOL)isalpha(character); - if (!isAlphaCharacter && numberOfAlphaCharactersFound < 1) - continue; - if (!isAlphaCharacter && numberOfAlphaCharactersFound > 0) - break; - - numberOfAlphaCharactersFound++; - if (numberOfAlphaCharactersFound == 1) { - if (character == 'F' || character == 'f') - return RSFebruary; - if (character == 'S' || character == 's') - return RSSeptember; - if (character == 'O' || character == 'o') - return RSOctober; - if (character == 'N' || character == 'n') - return RSNovember; - if (character == 'D' || character == 'd') - return RSDecember; - } - - monthCharacters[numberOfAlphaCharactersFound - 1] = character; - if (numberOfAlphaCharactersFound >=3) - break; - } - - if (numberOfAlphaCharactersFound < 2) - return NSNotFound; - - if (monthCharacters[0] == 'J' || monthCharacters[0] == 'j') { //Jan, Jun, Jul - if (monthCharacters[1] == 'a' || monthCharacters[1] == 'A') - return RSJanuary; - if (monthCharacters[1] == 'u' || monthCharacters[1] == 'U') { - if (monthCharacters[2] == 'n' || monthCharacters[2] == 'N') - return RSJune; - return RSJuly; - } - return RSJanuary; - } - - if (monthCharacters[0] == 'M' || monthCharacters[0] == 'm') { //March, May - if (monthCharacters[2] == 'y' || monthCharacters[2] == 'Y') - return RSMay; - return RSMarch; - } - - if (monthCharacters[0] == 'A' || monthCharacters[0] == 'a') { //April, August - if (monthCharacters[1] == 'u' || monthCharacters[1] == 'U') - return RSAugust; - return RSApril; - } - - return RSJanuary; //should never get here -} - - -static NSInteger nextNumericValue(const char *bytes, NSUInteger numberOfBytes, NSUInteger startingIndex, NSUInteger maximumNumberOfDigits, NSUInteger *finalIndex) { - - /*maximumNumberOfDigits has a maximum limit of 4 (for time zone offsets and years). - *finalIndex will be the index of the last character looked at.*/ - - if (maximumNumberOfDigits > 4) - maximumNumberOfDigits = 4; - - NSUInteger i = 0; - NSUInteger numberOfDigitsFound = 0; - NSInteger digits[4] = {0, 0, 0, 0}; - - for (i = startingIndex; i < numberOfBytes; i++) { - *finalIndex = i; - BOOL isDigit = (BOOL)isdigit(bytes[i]); - if (!isDigit && numberOfDigitsFound < 1) - continue; - if (!isDigit && numberOfDigitsFound > 0) - break; - digits[numberOfDigitsFound] = bytes[i] - 48; // '0' is 48 - numberOfDigitsFound++; - if (numberOfDigitsFound >= maximumNumberOfDigits) - break; - } - - if (numberOfDigitsFound < 1) - return NSNotFound; - if (numberOfDigitsFound == 1) - return digits[0]; - if (numberOfDigitsFound == 2) - return (digits[0] * 10) + digits[1]; - if (numberOfDigitsFound == 3) - return (digits[0] * 100) + (digits[1] * 10) + digits[2]; - return (digits[0] * 1000) + (digits[1] * 100) + (digits[2] * 10) + digits[3]; -} - - -static BOOL hasAtLeastOneAlphaCharacter(const char *s) { - - NSUInteger length = strlen(s); - NSUInteger i = 0; - - for (i = 0; i < length; i++) { - if (isalpha(s[i])) - return YES; - } - - return NO; -} - - -#pragma mark - Time Zones and offsets - -static NSInteger offsetInSecondsForTimeZoneAbbreviation(const char *abbreviation) { - - /*Linear search should be fine. It's a C array, and short (under 100 items). - Most common time zones are at the beginning of the array. (We can tweak this as needed.)*/ - - NSUInteger i; - - for (i = 0; i < kNumberOfTimeZones; i++) { - - RSTimeZoneAbbreviationAndOffset zone = timeZoneTable[i]; - if (strcmp(abbreviation, zone.abbreviation) == 0) { - if (zone.offsetHours < 0) - return (zone.offsetHours * 60 * 60) - (zone.offsetMinutes * 60); - return (zone.offsetHours * 60 * 60) + (zone.offsetMinutes * 60); - } - } - - return 0; -} - - -static NSInteger offsetInSecondsForOffsetCharacters(const char *timeZoneCharacters) { - - BOOL isPlus = timeZoneCharacters[0] == '+'; - NSUInteger finalIndex = 0; - NSInteger hours = nextNumericValue(timeZoneCharacters, strlen(timeZoneCharacters), 0, 2, &finalIndex); - NSInteger minutes = nextNumericValue(timeZoneCharacters, strlen(timeZoneCharacters), finalIndex + 1, 2, &finalIndex); - - if (hours == NSNotFound) - hours = 0; - if (minutes == NSNotFound) - minutes = 0; - if (hours == 0 && minutes == 0) - return 0; - - NSInteger seconds = (hours * 60 * 60) + (minutes * 60); - if (!isPlus) - seconds = 0 - seconds; - return seconds; -} - - -static const char *rs_GMT = "GMT"; -static const char *rs_UTC = "UTC"; - -static NSInteger parsedTimeZoneOffset(const char *bytes, NSUInteger numberOfBytes, NSUInteger startingIndex) { - - /*Examples: GMT Z +0000 -0000 +07:00 -0700 PDT EST - Parse into char[5] -- drop any colon characters. If numeric, calculate seconds from GMT. - If alpha, special-case GMT and Z, otherwise look up in time zone list to get offset.*/ - - char timeZoneCharacters[6] = {0, 0, 0, 0, 0, 0}; //nil-terminated last character - NSUInteger i = 0; - NSUInteger numberOfCharactersFound = 0; - - for (i = startingIndex; i < numberOfBytes; i++) { - char ch = bytes[i]; - if (ch == ':' || ch == ' ') - continue; - if (isdigit(ch) || isalpha(ch) || ch == '+' || ch == '-') { - numberOfCharactersFound++; - timeZoneCharacters[numberOfCharactersFound - 1] = ch; - } - if (numberOfCharactersFound >= 5) - break; - } - - if (numberOfCharactersFound < 1 || timeZoneCharacters[0] == 'Z' || timeZoneCharacters[0] == 'z') - return 0; - if (strcasestr(timeZoneCharacters, rs_GMT) != nil || strcasestr(timeZoneCharacters, rs_UTC)) - return 0; - - if (hasAtLeastOneAlphaCharacter(timeZoneCharacters)) - return offsetInSecondsForTimeZoneAbbreviation(timeZoneCharacters); - return offsetInSecondsForOffsetCharacters(timeZoneCharacters); -} - - -#pragma mark - Date Creation - -static NSDate *dateWithYearMonthDayHourMinuteSecondAndTimeZoneOffset(NSInteger year, NSInteger month, NSInteger day, NSInteger hour, NSInteger minute, NSInteger second, NSInteger milliseconds, NSInteger timeZoneOffset) { - - struct tm timeInfo; - timeInfo.tm_sec = (int)second; - timeInfo.tm_min = (int)minute; - timeInfo.tm_hour = (int)hour; - timeInfo.tm_mday = (int)day; - timeInfo.tm_mon = (int)(month - 1); //It's 1-based coming in - timeInfo.tm_year = (int)(year - 1900); //see time.h -- it's years since 1900 - timeInfo.tm_wday = -1; - timeInfo.tm_yday = -1; - timeInfo.tm_isdst = -1; - timeInfo.tm_gmtoff = 0;//[timeZone secondsFromGMT]; - timeInfo.tm_zone = nil; - - NSTimeInterval rawTime = (NSTimeInterval)(timegm(&timeInfo) - timeZoneOffset); //timegm instead of mktime (which uses local time zone) - if (rawTime == (time_t)ULONG_MAX) { - - /*NSCalendar is super-amazingly-slow (which is partly why RSDateParser exists), so this is used only when the date is far enough in the future (19 January 2038 03:14:08Z on 32-bit systems) that timegm fails. If profiling says that this is a performance issue, then you've got a weird app that needs to work with dates far in the future.*/ - - NSDateComponents *dateComponents = [NSDateComponents new]; - - dateComponents.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:timeZoneOffset]; - dateComponents.year = year; - dateComponents.month = month; - dateComponents.day = day; - dateComponents.hour = hour; - dateComponents.minute = minute; - dateComponents.second = second + (milliseconds / 1000); - - return [[NSCalendar autoupdatingCurrentCalendar] dateFromComponents:dateComponents]; - } - - if (milliseconds > 0) { - rawTime += ((float)milliseconds / 1000.0f); - } - - return [NSDate dateWithTimeIntervalSince1970:rawTime]; -} - - -#pragma mark - Standard Formats - -static NSDate *RSParsePubDateWithBytes(const char *bytes, NSUInteger numberOfBytes) { - - /*@"EEE',' dd MMM yyyy HH':'mm':'ss ZZZ" - @"EEE, dd MMM yyyy HH:mm:ss zzz" - @"dd MMM yyyy HH:mm zzz" - @"dd MMM yyyy HH:mm ZZZ" - @"EEE, dd MMM yyyy" - @"EEE, dd MMM yyyy HH:mm zzz" - etc.*/ - - NSUInteger finalIndex = 0; - NSInteger day = 1; - NSInteger month = RSJanuary; - NSInteger year = 1970; - NSInteger hour = 0; - NSInteger minute = 0; - NSInteger second = 0; - NSInteger timeZoneOffset = 0; - - day = nextNumericValue(bytes, numberOfBytes, 0, 2, &finalIndex); - if (day < 1 || day == NSNotFound) - day = 1; - - month = nextMonthValue(bytes, numberOfBytes, finalIndex + 1, &finalIndex); - year = nextNumericValue(bytes, numberOfBytes, finalIndex + 1, 4, &finalIndex); - hour = nextNumericValue(bytes, numberOfBytes, finalIndex + 1, 2, &finalIndex); - if (hour == NSNotFound) - hour = 0; - - minute = nextNumericValue(bytes, numberOfBytes, finalIndex + 1, 2, &finalIndex); - if (minute == NSNotFound) - minute = 0; - - NSUInteger currentIndex = finalIndex + 1; - - BOOL hasSeconds = (currentIndex < numberOfBytes) && (bytes[currentIndex] == ':'); - if (hasSeconds) - second = nextNumericValue(bytes, numberOfBytes, currentIndex, 2, &finalIndex); - - currentIndex = finalIndex + 1; - BOOL hasTimeZone = (currentIndex < numberOfBytes) && (bytes[currentIndex] == ' '); - if (hasTimeZone) - timeZoneOffset = parsedTimeZoneOffset(bytes, numberOfBytes, currentIndex); - - return dateWithYearMonthDayHourMinuteSecondAndTimeZoneOffset(year, month, day, hour, minute, second, 0, timeZoneOffset); -} - - -static NSDate *RSParseW3CWithBytes(const char *bytes, NSUInteger numberOfBytes) { - - /*@"yyyy'-'MM'-'dd'T'HH':'mm':'ss" - @"yyyy-MM-dd'T'HH:mm:sszzz" - @"yyyy-MM-dd'T'HH:mm:ss'.'SSSzzz" - etc.*/ - - NSUInteger finalIndex = 0; - NSInteger day = 1; - NSInteger month = RSJanuary; - NSInteger year = 1970; - NSInteger hour = 0; - NSInteger minute = 0; - NSInteger second = 0; - NSInteger milliseconds = 0; - NSInteger timeZoneOffset = 0; - - year = nextNumericValue(bytes, numberOfBytes, 0, 4, &finalIndex); - month = nextNumericValue(bytes, numberOfBytes, finalIndex + 1, 2, &finalIndex); - day = nextNumericValue(bytes, numberOfBytes, finalIndex + 1, 2, &finalIndex); - hour = nextNumericValue(bytes, numberOfBytes, finalIndex + 1, 2, &finalIndex); - minute = nextNumericValue(bytes, numberOfBytes, finalIndex + 1, 2, &finalIndex); - second = nextNumericValue(bytes, numberOfBytes, finalIndex + 1, 2, &finalIndex); - - NSUInteger currentIndex = finalIndex + 1; - BOOL hasMilliseconds = (currentIndex < numberOfBytes) && (bytes[currentIndex] == '.'); - if (hasMilliseconds) { - milliseconds = nextNumericValue(bytes, numberOfBytes, currentIndex, 3, &finalIndex); - currentIndex = finalIndex + 1; - } - - timeZoneOffset = parsedTimeZoneOffset(bytes, numberOfBytes, currentIndex); - - return dateWithYearMonthDayHourMinuteSecondAndTimeZoneOffset(year, month, day, hour, minute, second, milliseconds, timeZoneOffset); -} - - -static BOOL dateIsPubDate(const char *bytes, NSUInteger numberOfBytes) { - - NSUInteger i = 0; - - for (i = 0; i < numberOfBytes; i++) { - if (bytes[i] == ' ' || bytes[i] == ',') - return YES; - } - - return NO; -} - - -static BOOL dateIsW3CDate(const char *bytes, NSUInteger numberOfBytes) { - - // Something like 2010-11-17T08:40:07-05:00 - // But might be missing T character in the middle. - // Looks for four digits in a row followed by a -. - - for (NSUInteger i = 0; i < numberOfBytes; i++) { - char ch = bytes[i]; - if (ch == ' ' || ch == '\r' || ch == '\n' || ch == '\t') { - continue; - } - if (numberOfBytes - i < 5) { - return NO; - } - return isdigit(ch) && isdigit(bytes[i + 1]) && isdigit(bytes[i + 2]) && isdigit(bytes[i + 3]) && bytes[i + 4] == '-'; - } - - return NO; -} - -static BOOL numberOfBytesIsOutsideReasonableRange(NSUInteger numberOfBytes) { - return numberOfBytes < 6 || numberOfBytes > 150; -} - - -#pragma mark - API - -NSDate *RSDateWithBytes(const char *bytes, NSUInteger numberOfBytes) { - - if (numberOfBytesIsOutsideReasonableRange(numberOfBytes)) - return nil; - - if (dateIsW3CDate(bytes, numberOfBytes)) { - return RSParseW3CWithBytes(bytes, numberOfBytes); - } - if (dateIsPubDate(bytes, numberOfBytes)) - return RSParsePubDateWithBytes(bytes, numberOfBytes); - - // Fallback, in case our detection fails. - return RSParseW3CWithBytes(bytes, numberOfBytes); -} - - -NSDate *RSDateWithString(NSString *dateString) { - - const char *utf8String = [dateString UTF8String]; - return RSDateWithBytes(utf8String, strlen(utf8String)); -} - diff --git a/RSParser/Sources/ObjC/RSHTMLLinkParser.h b/RSParser/Sources/ObjC/RSHTMLLinkParser.h deleted file mode 100755 index 67c7f9f6c..000000000 --- a/RSParser/Sources/ObjC/RSHTMLLinkParser.h +++ /dev/null @@ -1,35 +0,0 @@ -// -// RSHTMLLinkParser.h -// RSParser -// -// Created by Brent Simmons on 8/7/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - -@import Foundation; - -NS_ASSUME_NONNULL_BEGIN - -/*Returns all some_text as RSHTMLLink object array.*/ - -@class ParserData; -@class RSHTMLLink; - -@interface RSHTMLLinkParser : NSObject - -+ (NSArray *)htmlLinksWithParserData:(ParserData *)parserData; - -@end - - -@interface RSHTMLLink : NSObject - -// Any of these, even urlString, may be nil, because HTML can be bad. - -@property (nonatomic, nullable, readonly) NSString *urlString; //absolute -@property (nonatomic, nullable, readonly) NSString *text; -@property (nonatomic, nullable, readonly) NSString *title; //title attribute inside anchor tag - -@end - -NS_ASSUME_NONNULL_END diff --git a/RSParser/Sources/ObjC/RSHTMLLinkParser.m b/RSParser/Sources/ObjC/RSHTMLLinkParser.m deleted file mode 100755 index 624e33569..000000000 --- a/RSParser/Sources/ObjC/RSHTMLLinkParser.m +++ /dev/null @@ -1,154 +0,0 @@ -// -// RSHTMLLinkParser.m -// RSParser -// -// Created by Brent Simmons on 8/7/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - - -#import "RSHTMLLinkParser.h" -#import "RSSAXHTMLParser.h" -#import "RSSAXParser.h" -#import "RSParserInternal.h" -#import "ParserData.h" - -#import - - - -@interface RSHTMLLinkParser() - -@property (nonatomic, readonly) NSMutableArray *links; -@property (nonatomic, readonly) ParserData *parserData; -@property (nonatomic, readonly) NSMutableArray *dictionaries; -@property (nonatomic, readonly) NSURL *baseURL; - -@end - - -@interface RSHTMLLink() - -@property (nonatomic, readwrite) NSString *urlString; //absolute -@property (nonatomic, readwrite) NSString *text; -@property (nonatomic, readwrite) NSString *title; //title attribute inside anchor tag - -@end - - -@implementation RSHTMLLinkParser - - -#pragma mark - Class Methods - -+ (NSArray *)htmlLinksWithParserData:(ParserData *)parserData { - - RSHTMLLinkParser *parser = [[self alloc] initWithParserData:parserData]; - return parser.links; -} - - -#pragma mark - Init - -- (instancetype)initWithParserData:(ParserData *)parserData { - - NSParameterAssert(parserData.data); - NSParameterAssert(parserData.url); - - self = [super init]; - if (!self) { - return nil; - } - - _links = [NSMutableArray new]; - _parserData = parserData; - _dictionaries = [NSMutableArray new]; - _baseURL = [NSURL URLWithString:parserData.url]; - - [self parse]; - - return self; -} - - -#pragma mark - Parse - -- (void)parse { - - RSSAXHTMLParser *parser = [[RSSAXHTMLParser alloc] initWithDelegate:self]; - [parser parseData:self.parserData.data]; - [parser finishParsing]; -} - - -- (RSHTMLLink *)currentLink { - - return self.links.lastObject; -} - - -static NSString *kHrefKey = @"href"; - -- (NSString *)urlStringFromDictionary:(NSDictionary *)d { - - NSString *href = [d rsparser_objectForCaseInsensitiveKey:kHrefKey]; - if (!href) { - return nil; - } - - NSURL *absoluteURL = [NSURL URLWithString:href relativeToURL:self.baseURL]; - return absoluteURL.absoluteString; -} - - -static NSString *kTitleKey = @"title"; - -- (NSString *)titleFromDictionary:(NSDictionary *)d { - - return [d rsparser_objectForCaseInsensitiveKey:kTitleKey]; -} - - -- (void)handleLinkAttributes:(NSDictionary *)d { - - RSHTMLLink *link = self.currentLink; - link.urlString = [self urlStringFromDictionary:d]; - link.title = [self titleFromDictionary:d]; -} - - -static const char *kAnchor = "a"; -static const NSInteger kAnchorLength = 2; - -- (void)saxParser:(RSSAXHTMLParser *)SAXParser XMLStartElement:(const xmlChar *)localName attributes:(const xmlChar **)attributes { - - if (!RSSAXEqualTags(localName, kAnchor, kAnchorLength)) { - return; - } - - RSHTMLLink *link = [RSHTMLLink new]; - [self.links addObject:link]; - - NSDictionary *d = [SAXParser attributesDictionary:attributes]; - if (!RSParserObjectIsEmpty(d)) { - [self handleLinkAttributes:d]; - } - - [SAXParser beginStoringCharacters]; -} - - -- (void)saxParser:(RSSAXParser *)SAXParser XMLEndElement:(const xmlChar *)localName { - - if (!RSSAXEqualTags(localName, kAnchor, kAnchorLength)) { - return; - } - - self.currentLink.text = SAXParser.currentStringWithTrimmedWhitespace; -} - -@end - -@implementation RSHTMLLink - -@end diff --git a/RSParser/Sources/ObjC/RSHTMLMetadata.h b/RSParser/Sources/ObjC/RSHTMLMetadata.h deleted file mode 100755 index 63df7a1db..000000000 --- a/RSParser/Sources/ObjC/RSHTMLMetadata.h +++ /dev/null @@ -1,97 +0,0 @@ -// -// RSHTMLMetadata.h -// RSParser -// -// Created by Brent Simmons on 3/6/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - -@import Foundation; -@import CoreGraphics; - -@class RSHTMLMetadataFeedLink; -@class RSHTMLMetadataAppleTouchIcon; -@class RSHTMLMetadataFavicon; -@class RSHTMLOpenGraphProperties; -@class RSHTMLOpenGraphImage; -@class RSHTMLTag; -@class RSHTMLTwitterProperties; - -NS_ASSUME_NONNULL_BEGIN - -@interface RSHTMLMetadata : NSObject - -- (instancetype)initWithURLString:(NSString *)urlString tags:(NSArray *)tags; - -@property (nonatomic, readonly) NSString *baseURLString; -@property (nonatomic, readonly) NSArray *tags; - -@property (nonatomic, readonly) NSArray *faviconLinks DEPRECATED_MSG_ATTRIBUTE("Use the favicons property instead."); -@property (nonatomic, readonly) NSArray *favicons; -@property (nonatomic, readonly) NSArray *appleTouchIcons; -@property (nonatomic, readonly) NSArray *feedLinks; - -@property (nonatomic, readonly) RSHTMLOpenGraphProperties *openGraphProperties; -@property (nonatomic, readonly) RSHTMLTwitterProperties *twitterProperties; - -@end - - -@interface RSHTMLMetadataAppleTouchIcon : NSObject - -@property (nonatomic, readonly) NSString *rel; -@property (nonatomic, nullable, readonly) NSString *sizes; -@property (nonatomic, readonly) CGSize size; -@property (nonatomic, nullable, readonly) NSString *urlString; // Absolute. - -@end - - -@interface RSHTMLMetadataFeedLink : NSObject - -@property (nonatomic, nullable, readonly) NSString *title; -@property (nonatomic, nullable, readonly) NSString *type; -@property (nonatomic, nullable, readonly) NSString *urlString; // Absolute. - -@end - -@interface RSHTMLMetadataFavicon : NSObject - -@property (nonatomic, nullable, readonly) NSString *type; -@property (nonatomic, nullable, readonly) NSString *urlString; - -@end - -@interface RSHTMLOpenGraphProperties : NSObject - -// TODO: the rest. At this writing (Nov. 26, 2017) I just care about og:image. -// See http://ogp.me/ - -- (instancetype)initWithURLString:(NSString *)urlString tags:(NSArray *)tags; - -@property (nonatomic, readonly) NSArray *images; - -@end - -@interface RSHTMLOpenGraphImage : NSObject - -@property (nonatomic, nullable, readonly) NSString *url; -@property (nonatomic, nullable, readonly) NSString *secureURL; -@property (nonatomic, nullable, readonly) NSString *mimeType; -@property (nonatomic, readonly) CGFloat width; -@property (nonatomic, readonly) CGFloat height; -@property (nonatomic, nullable, readonly) NSString *altText; - -@end - -@interface RSHTMLTwitterProperties : NSObject - -// TODO: the rest. At this writing (Nov. 26, 2017) I just care about twitter:image:src. - -- (instancetype)initWithURLString:(NSString *)urlString tags:(NSArray *)tags; - -@property (nonatomic, nullable, readonly) NSString *imageURL; // twitter:image:src - -@end - -NS_ASSUME_NONNULL_END diff --git a/RSParser/Sources/ObjC/RSHTMLMetadata.m b/RSParser/Sources/ObjC/RSHTMLMetadata.m deleted file mode 100755 index 2def0b078..000000000 --- a/RSParser/Sources/ObjC/RSHTMLMetadata.m +++ /dev/null @@ -1,483 +0,0 @@ -// -// RSHTMLMetadata.m -// RSParser -// -// Created by Brent Simmons on 3/6/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - -#import "RSHTMLMetadata.h" -#import "RSParserInternal.h" -#import "RSHTMLTag.h" - - - -static NSString *urlStringFromDictionary(NSDictionary *d); -static NSString *absoluteURLStringWithRelativeURLString(NSString *relativeURLString, NSString *baseURLString); -static NSString *absoluteURLStringWithDictionary(NSDictionary *d, NSString *baseURLString); -static NSArray *objectsOfClassWithTags(Class class, NSArray *tags, NSString *baseURLString); -static NSString *relValue(NSDictionary *d); -static BOOL typeIsFeedType(NSString *type); - -static NSString *kIconRelValue = @"icon"; -static NSString *kHrefKey = @"href"; -static NSString *kSrcKey = @"src"; -static NSString *kAppleTouchIconValue = @"apple-touch-icon"; -static NSString *kAppleTouchIconPrecomposedValue = @"apple-touch-icon-precomposed"; -static NSString *kSizesKey = @"sizes"; -static NSString *kTitleKey = @"title"; -static NSString *kRelKey = @"rel"; -static NSString *kAlternateKey = @"alternate"; -static NSString *kRSSSuffix = @"/rss+xml"; -static NSString *kAtomSuffix = @"/atom+xml"; -static NSString *kJSONSuffix = @"/json"; -static NSString *kTypeKey = @"type"; - -@interface RSHTMLMetadataAppleTouchIcon () - -- (instancetype)initWithTag:(RSHTMLTag *)tag baseURLString:(NSString *)baseURLString; - -@end - - -@interface RSHTMLMetadataFeedLink () - -- (instancetype)initWithTag:(RSHTMLTag *)tag baseURLString:(NSString *)baseURLString; - -@end - -@interface RSHTMLMetadataFavicon () - -- (instancetype)initWithTag:(RSHTMLTag *)tag baseURLString:(NSString *)baseURLString; - -@end - -@implementation RSHTMLMetadata - -#pragma mark - Init - -- (instancetype)initWithURLString:(NSString *)urlString tags:(NSArray *)tags { - - self = [super init]; - if (!self) { - return nil; - } - - _baseURLString = urlString; - _tags = tags; - - _favicons = [self resolvedFaviconLinks]; - - NSArray *appleTouchIconTags = [self appleTouchIconTags]; - _appleTouchIcons = objectsOfClassWithTags([RSHTMLMetadataAppleTouchIcon class], appleTouchIconTags, urlString); - - NSArray *feedLinkTags = [self feedLinkTags]; - _feedLinks = objectsOfClassWithTags([RSHTMLMetadataFeedLink class], feedLinkTags, urlString); - - _openGraphProperties = [[RSHTMLOpenGraphProperties alloc] initWithURLString:urlString tags:tags]; - _twitterProperties = [[RSHTMLTwitterProperties alloc] initWithURLString:urlString tags:tags]; - - return self; -} - -#pragma mark - Private - -- (NSArray *)linkTagsWithMatchingRel:(NSString *)valueToMatch { - - // Case-insensitive; matches a whitespace-delimited word - - NSMutableArray *tags = [NSMutableArray array]; - - for (RSHTMLTag *tag in self.tags) { - - if (tag.type != RSHTMLTagTypeLink || RSParserStringIsEmpty(urlStringFromDictionary(tag.attributes))) { - continue; - } - NSString *oneRelValue = relValue(tag.attributes); - if (oneRelValue) { - NSArray *relValues = [oneRelValue componentsSeparatedByCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet]; - - for (NSString *relValue in relValues) { - if ([relValue compare:valueToMatch options:NSCaseInsensitiveSearch] == NSOrderedSame) { - [tags addObject:tag]; - break; - } - } - } - } - - return tags; -} - - -- (NSArray *)appleTouchIconTags { - - NSMutableArray *tags = [NSMutableArray new]; - - for (RSHTMLTag *tag in self.tags) { - - if (tag.type != RSHTMLTagTypeLink) { - continue; - } - NSString *oneRelValue = relValue(tag.attributes).lowercaseString; - if ([oneRelValue isEqualToString:kAppleTouchIconValue] || [oneRelValue isEqualToString:kAppleTouchIconPrecomposedValue]) { - [tags addObject:tag]; - } - } - - return tags; -} - - -- (NSArray *)feedLinkTags { - - NSMutableArray *tags = [NSMutableArray new]; - - for (RSHTMLTag *tag in self.tags) { - - if (tag.type != RSHTMLTagTypeLink) { - continue; - } - - NSDictionary *oneDictionary = tag.attributes; - NSString *oneRelValue = relValue(oneDictionary).lowercaseString; - if (![oneRelValue isEqualToString:kAlternateKey]) { - continue; - } - - NSString *oneType = [oneDictionary rsparser_objectForCaseInsensitiveKey:kTypeKey]; - if (!typeIsFeedType(oneType)) { - continue; - } - - if (RSParserStringIsEmpty(urlStringFromDictionary(oneDictionary))) { - continue; - } - - [tags addObject:tag]; - } - - return tags; -} - -- (NSArray *)faviconLinks { - NSMutableArray *urls = [NSMutableArray array]; - - for (RSHTMLMetadataFavicon *favicon in self.favicons) { - [urls addObject:favicon.urlString]; - } - - return urls; -} - -- (NSArray *)resolvedFaviconLinks { - NSArray *tags = [self linkTagsWithMatchingRel:kIconRelValue]; - NSMutableArray *links = [NSMutableArray array]; - NSMutableSet *seenHrefs = [NSMutableSet setWithCapacity:tags.count]; - - for (RSHTMLTag *tag in tags) { - RSHTMLMetadataFavicon *link = [[RSHTMLMetadataFavicon alloc] initWithTag:tag baseURLString:self.baseURLString]; - NSString *urlString = link.urlString; - if (urlString == nil) { - continue; - } - if (![seenHrefs containsObject:urlString]) { - [links addObject:link]; - [seenHrefs addObject:urlString]; - } - } - - return links; -} - -@end - - -static NSString *relValue(NSDictionary *d) { - - return [d rsparser_objectForCaseInsensitiveKey:kRelKey]; -} - - -static NSString *urlStringFromDictionary(NSDictionary *d) { - - NSString *urlString = [d rsparser_objectForCaseInsensitiveKey:kHrefKey]; - if (urlString) { - return urlString; - } - - return [d rsparser_objectForCaseInsensitiveKey:kSrcKey]; -} - - -static NSString *absoluteURLStringWithRelativeURLString(NSString *relativeURLString, NSString *baseURLString) { - - NSURL *url = [NSURL URLWithString:baseURLString]; - if (!url) { - return nil; - } - - NSURL *absoluteURL = [NSURL URLWithString:relativeURLString relativeToURL:url]; - return absoluteURL.absoluteURL.standardizedURL.absoluteString; -} - - -static NSString *absoluteURLStringWithDictionary(NSDictionary *d, NSString *baseURLString) { - - NSString *urlString = urlStringFromDictionary(d); - if (RSParserStringIsEmpty(urlString)) { - return nil; - } - return absoluteURLStringWithRelativeURLString(urlString, baseURLString); -} - - -static NSArray *objectsOfClassWithTags(Class class, NSArray *tags, NSString *baseURLString) { - - NSMutableArray *objects = [NSMutableArray new]; - - for (RSHTMLTag *tag in tags) { - - id oneObject = [[class alloc] initWithTag:tag baseURLString:baseURLString]; - if (oneObject) { - [objects addObject:oneObject]; - } - } - - return objects; -} - - -static BOOL typeIsFeedType(NSString *type) { - - type = type.lowercaseString; - return [type hasSuffix:kRSSSuffix] || [type hasSuffix:kAtomSuffix] || [type hasSuffix:kJSONSuffix]; -} - - -@implementation RSHTMLMetadataAppleTouchIcon - -- (instancetype)initWithTag:(RSHTMLTag *)tag baseURLString:(NSString *)baseURLString { - - self = [super init]; - if (!self) { - return nil; - } - - NSDictionary *d = tag.attributes; - _urlString = absoluteURLStringWithDictionary(d, baseURLString); - _sizes = [d rsparser_objectForCaseInsensitiveKey:kSizesKey]; - _rel = [d rsparser_objectForCaseInsensitiveKey:kRelKey]; - - _size = CGSizeZero; - if (_sizes) { - NSArray *components = [_sizes componentsSeparatedByString:@"x"]; - if (components.count == 2) { - CGFloat width = [components[0] floatValue]; - CGFloat height = [components[1] floatValue]; - _size = CGSizeMake(width, height); - } - } - - return self; -} - -@end - - -@implementation RSHTMLMetadataFeedLink - -- (instancetype)initWithTag:(RSHTMLTag *)tag baseURLString:(NSString *)baseURLString { - - self = [super init]; - if (!self) { - return nil; - } - - NSDictionary *d = tag.attributes; - _urlString = absoluteURLStringWithDictionary(d, baseURLString); - _title = [d rsparser_objectForCaseInsensitiveKey:kTitleKey]; - _type = [d rsparser_objectForCaseInsensitiveKey:kTypeKey]; - - return self; -} - -@end - -@implementation RSHTMLMetadataFavicon - -- (instancetype)initWithTag:(RSHTMLTag *)tag baseURLString:(NSString *)baseURLString { - - self = [super init]; - if (!self) { - return nil; - } - - NSDictionary *d = tag.attributes; - _urlString = absoluteURLStringWithDictionary(d, baseURLString); - _type = [d rsparser_objectForCaseInsensitiveKey:kTypeKey]; - - return self; -} - -@end - -@interface RSHTMLOpenGraphImage () - -@property (nonatomic, readwrite) NSString *url; -@property (nonatomic, readwrite) NSString *secureURL; -@property (nonatomic, readwrite) NSString *mimeType; -@property (nonatomic, readwrite) CGFloat width; -@property (nonatomic, readwrite) CGFloat height; -@property (nonatomic, readwrite) NSString *altText; - -@end - -@implementation RSHTMLOpenGraphImage - - -@end - -@interface RSHTMLOpenGraphProperties () - -@property (nonatomic) NSMutableArray *ogImages; -@end - -@implementation RSHTMLOpenGraphProperties - -- (instancetype)initWithURLString:(NSString *)urlString tags:(NSArray *)tags { - - self = [super init]; - if (!self) { - return nil; - } - - _ogImages = [NSMutableArray new]; - - [self parseTags:tags]; - return self; -} - - -- (RSHTMLOpenGraphImage *)currentImage { - - return self.ogImages.lastObject; -} - - -- (RSHTMLOpenGraphImage *)pushImage { - - RSHTMLOpenGraphImage *image = [RSHTMLOpenGraphImage new]; - [self.ogImages addObject:image]; - return image; -} - -- (RSHTMLOpenGraphImage *)ensureImage { - - RSHTMLOpenGraphImage *image = [self currentImage]; - if (image != nil) { - return image; - } - return [self pushImage]; -} - - -- (NSArray *)images { - - return self.ogImages; -} - -static NSString *ogPrefix = @"og:"; -static NSString *ogImage = @"og:image"; -static NSString *ogImageURL = @"og:image:url"; -static NSString *ogImageSecureURL = @"og:image:secure_url"; -static NSString *ogImageType = @"og:image:type"; -static NSString *ogImageWidth = @"og:image:width"; -static NSString *ogImageHeight = @"og:image:height"; -static NSString *ogImageAlt = @"og:image:alt"; -static NSString *ogPropertyKey = @"property"; -static NSString *ogContentKey = @"content"; - -- (void)parseTags:(NSArray *)tags { - - for (RSHTMLTag *tag in tags) { - - if (tag.type != RSHTMLTagTypeMeta) { - continue; - } - - NSString *propertyName = tag.attributes[ogPropertyKey]; - if (!propertyName || ![propertyName hasPrefix:ogPrefix]) { - continue; - } - NSString *content = tag.attributes[ogContentKey]; - if (!content) { - continue; - } - - if ([propertyName isEqualToString:ogImage]) { - RSHTMLOpenGraphImage *image = [self currentImage]; - if (!image || image.url) { // Most likely case, since og:image will probably appear before other image attributes. - image = [self pushImage]; - } - image.url = content; - } - - else if ([propertyName isEqualToString:ogImageURL]) { - [self ensureImage].url = content; - } - else if ([propertyName isEqualToString:ogImageSecureURL]) { - [self ensureImage].secureURL = content; - } - else if ([propertyName isEqualToString:ogImageType]) { - [self ensureImage].mimeType = content; - } - else if ([propertyName isEqualToString:ogImageAlt]) { - [self ensureImage].altText = content; - } - else if ([propertyName isEqualToString:ogImageWidth]) { - [self ensureImage].width = [content floatValue]; - } - else if ([propertyName isEqualToString:ogImageHeight]) { - [self ensureImage].height = [content floatValue]; - } - } -} - -@end - -@implementation RSHTMLTwitterProperties - -static NSString *twitterNameKey = @"name"; -static NSString *twitterContentKey = @"content"; -static NSString *twitterImageSrc = @"twitter:image:src"; - -- (instancetype)initWithURLString:(NSString *)urlString tags:(NSArray *)tags { - - self = [super init]; - if (!self) { - return nil; - } - - for (RSHTMLTag *tag in tags) { - - if (tag.type != RSHTMLTagTypeMeta) { - continue; - } - NSString *name = tag.attributes[twitterNameKey]; - if (!name || ![name isEqualToString:twitterImageSrc]) { - continue; - } - NSString *content = tag.attributes[twitterContentKey]; - if (!content || content.length < 1) { - continue; - } - _imageURL = content; - break; - } - - return self; -} - -@end - diff --git a/RSParser/Sources/ObjC/RSHTMLMetadataParser.h b/RSParser/Sources/ObjC/RSHTMLMetadataParser.h deleted file mode 100755 index f9361905c..000000000 --- a/RSParser/Sources/ObjC/RSHTMLMetadataParser.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// RSHTMLMetadataParser.h -// RSParser -// -// Created by Brent Simmons on 3/6/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - -@import Foundation; - - -@class RSHTMLMetadata; -@class ParserData; - -NS_ASSUME_NONNULL_BEGIN - -@interface RSHTMLMetadataParser : NSObject - -+ (RSHTMLMetadata *)HTMLMetadataWithParserData:(ParserData *)parserData; - - -@end - -NS_ASSUME_NONNULL_END diff --git a/RSParser/Sources/ObjC/RSHTMLMetadataParser.m b/RSParser/Sources/ObjC/RSHTMLMetadataParser.m deleted file mode 100755 index 254fd109d..000000000 --- a/RSParser/Sources/ObjC/RSHTMLMetadataParser.m +++ /dev/null @@ -1,151 +0,0 @@ -// -// RSHTMLMetadataParser.m -// RSParser -// -// Created by Brent Simmons on 3/6/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - -#import "RSHTMLMetadataParser.h" -#import "RSHTMLMetadata.h" -#import "RSSAXHTMLParser.h" -#import "RSSAXHTMLParser.h" -#import "RSSAXParser.h" -#import "RSParserInternal.h" -#import "ParserData.h" -#import "RSHTMLTag.h" - -#import - - -@interface RSHTMLMetadataParser () - -@property (nonatomic, readonly) ParserData *parserData; -@property (nonatomic, readwrite) RSHTMLMetadata *metadata; -@property (nonatomic) NSMutableArray *tags; -@property (nonatomic) BOOL didFinishParsing; -@property (nonatomic) BOOL shouldScanPastHeadSection; - -@end - - -@implementation RSHTMLMetadataParser - - -#pragma mark - Class Methods - -+ (RSHTMLMetadata *)HTMLMetadataWithParserData:(ParserData *)parserData { - - RSHTMLMetadataParser *parser = [[self alloc] initWithParserData:parserData]; - return parser.metadata; -} - - -#pragma mark - Init - -- (instancetype)initWithParserData:(ParserData *)parserData { - - NSParameterAssert(parserData.data); - NSParameterAssert(parserData.url); - - self = [super init]; - if (!self) { - return nil; - } - - _parserData = parserData; - _tags = [NSMutableArray new]; - - // YouTube has a weird bug where, on some pages, it puts the feed link tag after the head section, in the body section. - // This allows for a special case where we continue to scan after the head section. - // (Yes, this match could yield false positives, but it’s harmless.) - _shouldScanPastHeadSection = [parserData.url rangeOfString:@"youtube" options:NSCaseInsensitiveSearch].location != NSNotFound; - - [self parse]; - - return self; -} - - -#pragma mark - Parse - -- (void)parse { - - RSSAXHTMLParser *parser = [[RSSAXHTMLParser alloc] initWithDelegate:self]; - [parser parseData:self.parserData.data]; - [parser finishParsing]; - - self.metadata = [[RSHTMLMetadata alloc] initWithURLString:self.parserData.url tags:self.tags]; -} - - -static NSString *kHrefKey = @"href"; -static NSString *kSrcKey = @"src"; -static NSString *kRelKey = @"rel"; - -- (NSString *)linkForDictionary:(NSDictionary *)d { - - NSString *link = [d rsparser_objectForCaseInsensitiveKey:kHrefKey]; - if (link) { - return link; - } - - return [d rsparser_objectForCaseInsensitiveKey:kSrcKey]; -} - -- (void)handleLinkAttributes:(NSDictionary *)d { - - if (RSParserStringIsEmpty([d rsparser_objectForCaseInsensitiveKey:kRelKey])) { - return; - } - if (RSParserStringIsEmpty([self linkForDictionary:d])) { - return; - } - - RSHTMLTag *tag = [RSHTMLTag linkTagWithAttributes:d]; - [self.tags addObject:tag]; -} - -- (void)handleMetaAttributes:(NSDictionary *)d { - - RSHTMLTag *tag = [RSHTMLTag metaTagWithAttributes:d]; - [self.tags addObject:tag]; -} - -#pragma mark - RSSAXHTMLParserDelegate - -static const char *kBody = "body"; -static const NSInteger kBodyLength = 5; -static const char *kLink = "link"; -static const NSInteger kLinkLength = 5; -static const char *kMeta = "meta"; -static const NSInteger kMetaLength = 5; - -- (void)saxParser:(RSSAXHTMLParser *)SAXParser XMLStartElement:(const xmlChar *)localName attributes:(const xmlChar **)attributes { - - if (self.didFinishParsing) { - return; - } - - if (RSSAXEqualTags(localName, kBody, kBodyLength) && !self.shouldScanPastHeadSection) { - self.didFinishParsing = YES; - return; - } - - if (RSSAXEqualTags(localName, kLink, kLinkLength)) { - NSDictionary *d = [SAXParser attributesDictionary:attributes]; - if (!RSParserObjectIsEmpty(d)) { - [self handleLinkAttributes:d]; - } - return; - } - - if (RSSAXEqualTags(localName, kMeta, kMetaLength)) { - NSDictionary *d = [SAXParser attributesDictionary:attributes]; - if (!RSParserObjectIsEmpty(d)) { - [self handleMetaAttributes:d]; - } - } -} - -@end diff --git a/RSParser/Sources/ObjC/RSHTMLTag.h b/RSParser/Sources/ObjC/RSHTMLTag.h deleted file mode 100644 index e8e9cb426..000000000 --- a/RSParser/Sources/ObjC/RSHTMLTag.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// RSHTMLTag.h -// RSParser -// -// Created by Brent Simmons on 11/26/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -@import Foundation; - -NS_ASSUME_NONNULL_BEGIN - -extern NSString *RSHTMLTagNameLink; // @"link" -extern NSString *RSHTMLTagNameMeta; // @"meta" - -typedef NS_ENUM(NSInteger, RSHTMLTagType) { - RSHTMLTagTypeLink, - RSHTMLTagTypeMeta -}; - -@interface RSHTMLTag : NSObject - -- (instancetype)initWithType:(RSHTMLTagType)type attributes:(NSDictionary *)attributes; - -+ (RSHTMLTag *)linkTagWithAttributes:(NSDictionary *)attributes; -+ (RSHTMLTag *)metaTagWithAttributes:(NSDictionary *)attributes; - -@property (nonatomic, readonly) RSHTMLTagType type; -@property (nonatomic, readonly) NSDictionary *attributes; - -@end - -NS_ASSUME_NONNULL_END diff --git a/RSParser/Sources/ObjC/RSHTMLTag.m b/RSParser/Sources/ObjC/RSHTMLTag.m deleted file mode 100644 index 5b0262ffb..000000000 --- a/RSParser/Sources/ObjC/RSHTMLTag.m +++ /dev/null @@ -1,43 +0,0 @@ -// -// RSHTMLTag.m -// RSParser -// -// Created by Brent Simmons on 11/26/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -#import "RSHTMLTag.h" - -NSString *RSHTMLTagNameLink = @"link"; -NSString *RSHTMLTagNameMeta = @"meta"; - -@implementation RSHTMLTag - -- (instancetype)initWithType:(RSHTMLTagType)type attributes:(NSDictionary *)attributes { - - self = [super init]; - if (!self) { - return nil; - } - - _type = type; - _attributes = attributes; - - return self; -} - -+ (RSHTMLTag *)linkTagWithAttributes:(NSDictionary *)attributes { - - return [[self alloc] initWithType:RSHTMLTagTypeLink attributes:attributes]; -} - -+ (RSHTMLTag *)metaTagWithAttributes:(NSDictionary *)attributes { - - return [[self alloc] initWithType:RSHTMLTagTypeMeta attributes:attributes]; -} - -- (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p> type: %ld attributes: %@", NSStringFromClass([self class]), self, (long)self.type, self.attributes]; -} - -@end diff --git a/RSParser/Sources/ObjC/RSOPMLAttributes.h b/RSParser/Sources/ObjC/RSOPMLAttributes.h deleted file mode 100755 index 688132fe5..000000000 --- a/RSParser/Sources/ObjC/RSOPMLAttributes.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// RSOPMLAttributes.h -// RSParser -// -// Created by Brent Simmons on 2/28/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - -@import Foundation; - -// OPML allows for arbitrary attributes. -// These are the common attributes in OPML files used as RSS subscription lists. - -extern NSString *OPMLTextKey; //text -extern NSString *OPMLTitleKey; //title -extern NSString *OPMLDescriptionKey; //description -extern NSString *OPMLTypeKey; //type -extern NSString *OPMLVersionKey; //version -extern NSString *OPMLHMTLURLKey; //htmlUrl -extern NSString *OPMLXMLURLKey; //xmlUrl - - -@interface NSDictionary (RSOPMLAttributes) - -// A frequent error in OPML files is to mess up the capitalization, -// so these do a case-insensitive lookup. - -@property (nonatomic, readonly) NSString *opml_text; -@property (nonatomic, readonly) NSString *opml_title; -@property (nonatomic, readonly) NSString *opml_description; -@property (nonatomic, readonly) NSString *opml_type; -@property (nonatomic, readonly) NSString *opml_version; -@property (nonatomic, readonly) NSString *opml_htmlUrl; -@property (nonatomic, readonly) NSString *opml_xmlUrl; - -@end diff --git a/RSParser/Sources/ObjC/RSOPMLAttributes.m b/RSParser/Sources/ObjC/RSOPMLAttributes.m deleted file mode 100755 index db6508b24..000000000 --- a/RSParser/Sources/ObjC/RSOPMLAttributes.m +++ /dev/null @@ -1,68 +0,0 @@ -// -// RSOPMLAttributes.m -// RSParser -// -// Created by Brent Simmons on 2/28/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - -#import "RSOPMLAttributes.h" -#import "RSParserInternal.h" - - - - -NSString *OPMLTextKey = @"text"; -NSString *OPMLTitleKey = @"title"; -NSString *OPMLDescriptionKey = @"description"; -NSString *OPMLTypeKey = @"type"; -NSString *OPMLVersionKey = @"version"; -NSString *OPMLHMTLURLKey = @"htmlUrl"; -NSString *OPMLXMLURLKey = @"xmlUrl"; - - -@implementation NSDictionary (RSOPMLAttributes) - -- (NSString *)opml_text { - - return [self rsparser_objectForCaseInsensitiveKey:OPMLTextKey]; -} - - -- (NSString *)opml_title { - - return [self rsparser_objectForCaseInsensitiveKey:OPMLTitleKey]; -} - - -- (NSString *)opml_description { - - return [self rsparser_objectForCaseInsensitiveKey:OPMLDescriptionKey]; -} - - -- (NSString *)opml_type { - - return [self rsparser_objectForCaseInsensitiveKey:OPMLTypeKey]; -} - - -- (NSString *)opml_version { - - return [self rsparser_objectForCaseInsensitiveKey:OPMLVersionKey]; -} - - -- (NSString *)opml_htmlUrl { - - return [self rsparser_objectForCaseInsensitiveKey:OPMLHMTLURLKey]; -} - - -- (NSString *)opml_xmlUrl { - - return [self rsparser_objectForCaseInsensitiveKey:OPMLXMLURLKey]; -} - - -@end diff --git a/RSParser/Sources/ObjC/RSOPMLDocument.h b/RSParser/Sources/ObjC/RSOPMLDocument.h deleted file mode 100755 index 5061853fe..000000000 --- a/RSParser/Sources/ObjC/RSOPMLDocument.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// RSOPMLDocument.h -// RSParser -// -// Created by Brent Simmons on 2/28/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - -@import Foundation; - -#import "RSOPMLItem.h" - - - - -@interface RSOPMLDocument : RSOPMLItem - -@property (nonatomic) NSString *title; -@property (nonatomic) NSString *url; - -@end diff --git a/RSParser/Sources/ObjC/RSOPMLDocument.m b/RSParser/Sources/ObjC/RSOPMLDocument.m deleted file mode 100755 index 1506bd911..000000000 --- a/RSParser/Sources/ObjC/RSOPMLDocument.m +++ /dev/null @@ -1,14 +0,0 @@ -// -// RSOPMLDocument.m -// RSParser -// -// Created by Brent Simmons on 2/28/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - - -#import "RSOPMLDocument.h" - -@implementation RSOPMLDocument - -@end diff --git a/RSParser/Sources/ObjC/RSOPMLError.h b/RSParser/Sources/ObjC/RSOPMLError.h deleted file mode 100755 index 276c62ed7..000000000 --- a/RSParser/Sources/ObjC/RSOPMLError.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// RSOPMLError.h -// RSParser -// -// Created by Brent Simmons on 2/28/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - -@import Foundation; - -extern NSString *RSOPMLErrorDomain; - - -typedef NS_ENUM(NSInteger, RSOPMLErrorCode) { - RSOPMLErrorCodeDataIsWrongFormat = 1024 -}; - - -NSError *RSOPMLWrongFormatError(NSString *fileName); diff --git a/RSParser/Sources/ObjC/RSOPMLError.m b/RSParser/Sources/ObjC/RSOPMLError.m deleted file mode 100755 index 7aa3c5e9d..000000000 --- a/RSParser/Sources/ObjC/RSOPMLError.m +++ /dev/null @@ -1,22 +0,0 @@ -// -// RSOPMLError.m -// RSParser -// -// Created by Brent Simmons on 2/28/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - -#import "RSOPMLError.h" - -NSString *RSOPMLErrorDomain = @"com.ranchero.OPML"; - -NSError *RSOPMLWrongFormatError(NSString *fileName) { - - NSString *localizedDescriptionFormatString = NSLocalizedString(@"The file ‘%@’ can’t be parsed because it’s not an OPML file.", @"OPML wrong format"); - NSString *localizedDescription = [NSString stringWithFormat:localizedDescriptionFormatString, fileName]; - - NSString *localizedFailureString = NSLocalizedString(@"The file is not an OPML file.", @"OPML wrong format"); - NSDictionary *userInfo = @{NSLocalizedDescriptionKey: localizedDescription, NSLocalizedFailureReasonErrorKey: localizedFailureString}; - - return [[NSError alloc] initWithDomain:RSOPMLErrorDomain code:RSOPMLErrorCodeDataIsWrongFormat userInfo:userInfo]; -} diff --git a/RSParser/Sources/ObjC/RSOPMLFeedSpecifier.h b/RSParser/Sources/ObjC/RSOPMLFeedSpecifier.h deleted file mode 100755 index 8c4aea6b0..000000000 --- a/RSParser/Sources/ObjC/RSOPMLFeedSpecifier.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// RSOPMLFeedSpecifier.h -// RSParser -// -// Created by Brent Simmons on 2/28/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - -@import Foundation; - -NS_ASSUME_NONNULL_BEGIN - -@interface RSOPMLFeedSpecifier : NSObject - -- (instancetype)initWithTitle:(NSString * _Nullable)title feedDescription:(NSString * _Nullable)feedDescription homePageURL:(NSString * _Nullable)homePageURL feedURL:(NSString *)feedURL; - -@property (nonatomic, nullable, readonly) NSString *title; -@property (nonatomic, nullable, readonly) NSString *feedDescription; -@property (nonatomic, nullable, readonly) NSString *homePageURL; -@property (nonatomic, readonly) NSString *feedURL; - -@end - -NS_ASSUME_NONNULL_END diff --git a/RSParser/Sources/ObjC/RSOPMLFeedSpecifier.m b/RSParser/Sources/ObjC/RSOPMLFeedSpecifier.m deleted file mode 100755 index bb32ccf54..000000000 --- a/RSParser/Sources/ObjC/RSOPMLFeedSpecifier.m +++ /dev/null @@ -1,51 +0,0 @@ -// -// RSOPMLFeedSpecifier.m -// RSParser -// -// Created by Brent Simmons on 2/28/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - -#import "RSOPMLFeedSpecifier.h" -#import "RSParserInternal.h" - - - -@implementation RSOPMLFeedSpecifier - -- (instancetype)initWithTitle:(NSString *)title feedDescription:(NSString *)feedDescription homePageURL:(NSString *)homePageURL feedURL:(NSString *)feedURL { - - NSParameterAssert(!RSParserStringIsEmpty(feedURL)); - - self = [super init]; - if (!self) { - return nil; - } - - if (RSParserStringIsEmpty(title)) { - _title = nil; - } - else { - _title = title; - } - - if (RSParserStringIsEmpty(feedDescription)) { - _feedDescription = nil; - } - else { - _feedDescription = feedDescription; - } - - if (RSParserStringIsEmpty(homePageURL)) { - _homePageURL = nil; - } - else { - _homePageURL = homePageURL; - } - - _feedURL = feedURL; - - return self; -} - -@end diff --git a/RSParser/Sources/ObjC/RSOPMLItem.h b/RSParser/Sources/ObjC/RSOPMLItem.h deleted file mode 100755 index 15afa48f2..000000000 --- a/RSParser/Sources/ObjC/RSOPMLItem.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// RSOPMLItem.h -// RSParser -// -// Created by Brent Simmons on 2/28/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - -@import Foundation; - -@class RSOPMLFeedSpecifier; - -NS_ASSUME_NONNULL_BEGIN - -@interface RSOPMLItem : NSObject - -@property (nonatomic, nullable) NSDictionary *attributes; -@property (nonatomic, nullable) NSArray *children; - -- (void)addChild:(RSOPMLItem *)child; - -@property (nonatomic, nullable, readonly) RSOPMLFeedSpecifier *feedSpecifier; - -@property (nonatomic, nullable, readonly) NSString *titleFromAttributes; -@property (nonatomic, readonly) BOOL isFolder; - -@end - -NS_ASSUME_NONNULL_END - diff --git a/RSParser/Sources/ObjC/RSOPMLItem.m b/RSParser/Sources/ObjC/RSOPMLItem.m deleted file mode 100755 index a273cd317..000000000 --- a/RSParser/Sources/ObjC/RSOPMLItem.m +++ /dev/null @@ -1,87 +0,0 @@ -// -// RSOPMLItem.m -// RSParser -// -// Created by Brent Simmons on 2/28/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - -#import "RSOPMLItem.h" -#import "RSOPMLAttributes.h" -#import "RSOPMLFeedSpecifier.h" -#import "RSParserInternal.h" - - - -@interface RSOPMLItem () - -@property (nonatomic) NSMutableArray *mutableChildren; - -@end - - -@implementation RSOPMLItem - -@synthesize children = _children; -@synthesize feedSpecifier = _feedSpecifier; - - -- (NSArray *)children { - - return [self.mutableChildren copy]; -} - - -- (void)setChildren:(NSArray *)children { - - _children = children; - self.mutableChildren = [_children mutableCopy]; -} - - -- (void)addChild:(RSOPMLItem *)child { - - if (!self.mutableChildren) { - self.mutableChildren = [NSMutableArray new]; - } - - [self.mutableChildren addObject:child]; -} - - -- (RSOPMLFeedSpecifier *)feedSpecifier { - - if (_feedSpecifier) { - return _feedSpecifier; - } - - NSString *feedURL = self.attributes.opml_xmlUrl; - if (RSParserObjectIsEmpty(feedURL)) { - return nil; - } - - _feedSpecifier = [[RSOPMLFeedSpecifier alloc] initWithTitle:self.titleFromAttributes feedDescription:self.attributes.opml_description homePageURL:self.attributes.opml_htmlUrl feedURL:feedURL]; - - return _feedSpecifier; -} - -- (NSString *)titleFromAttributes { - - NSString *title = self.attributes.opml_title; - if (title) { - return title; - } - title = self.attributes.opml_text; - if (title) { - return title; - } - - return nil; -} - -- (BOOL)isFolder { - - return self.mutableChildren.count > 0; -} - -@end diff --git a/RSParser/Sources/ObjC/RSOPMLParser.h b/RSParser/Sources/ObjC/RSOPMLParser.h deleted file mode 100755 index 8db594b03..000000000 --- a/RSParser/Sources/ObjC/RSOPMLParser.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// RSOPMLParser.h -// RSParser -// -// Created by Brent Simmons on 7/12/15. -// Copyright © 2015 Ranchero Software, LLC. All rights reserved. -// - -@import Foundation; - - -@class ParserData; -@class RSOPMLDocument; - -typedef void (^OPMLParserCallback)(RSOPMLDocument *opmlDocument, NSError *error); - -// Parses on background thread; calls back on main thread. -void RSParseOPML(ParserData *parserData, OPMLParserCallback callback); - - -@interface RSOPMLParser: NSObject - -+ (RSOPMLDocument *)parseOPMLWithParserData:(ParserData *)parserData error:(NSError **)error; - -@end - diff --git a/RSParser/Sources/ObjC/RSOPMLParser.m b/RSParser/Sources/ObjC/RSOPMLParser.m deleted file mode 100755 index 93f2c420c..000000000 --- a/RSParser/Sources/ObjC/RSOPMLParser.m +++ /dev/null @@ -1,310 +0,0 @@ -// -// RSOPMLParser.m -// RSParser -// -// Created by Brent Simmons on 7/12/15. -// Copyright © 2015 Ranchero Software, LLC. All rights reserved. -// - -#import "RSOPMLParser.h" -#import "RSSAXParser.h" -#import "RSOPMLItem.h" -#import "RSOPMLDocument.h" -#import "RSOPMLAttributes.h" -#import "RSOPMLError.h" -#import "RSOPMLParser.h" -#import "ParserData.h" - -#import - - - -@interface RSOPMLParser () - -@property (nonatomic, readwrite) RSOPMLDocument *OPMLDocument; -@property (nonatomic, readwrite) NSError *error; -@property (nonatomic) NSMutableArray *itemStack; - -@end - -void RSParseOPML(ParserData *parserData, OPMLParserCallback callback) { - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - - @autoreleasepool { - NSError *error = nil; - RSOPMLDocument *opmlDocument = [RSOPMLParser parseOPMLWithParserData:parserData error:&error]; - - dispatch_async(dispatch_get_main_queue(), ^{ - callback(opmlDocument, error); - }); - } - }); -} - -@implementation RSOPMLParser - -#pragma mark - Class Methods - -+ (RSOPMLDocument *)parseOPMLWithParserData:(ParserData *)parserData error:(NSError **)error { - - RSOPMLParser *parser = [[RSOPMLParser alloc] initWithParserData:parserData]; - - RSOPMLDocument *document = parser.OPMLDocument; - document.url = parserData.url; - if (parser.error && error) { - *error = parser.error; - return nil; - } - return document; -} - -#pragma mark - Init - -- (instancetype)initWithParserData:(ParserData *)parserData { - - self = [super init]; - if (!self) { - return nil; - } - - [self parse:parserData]; - - return self; -} - - -#pragma mark - Private - -- (void)parse:(ParserData *)parserData { - - @autoreleasepool { - - if (![self canParseData:parserData.data]) { - - NSString *filename = nil; - NSURL *url = [NSURL URLWithString:parserData.url]; - if (url && url.isFileURL) { - filename = url.path.lastPathComponent; - } - if ([parserData.url hasPrefix:@"http"]) { - filename = parserData.url; - } - if (!filename) { - filename = parserData.url; - } - self.error = RSOPMLWrongFormatError(filename); - return; - } - - RSSAXParser *parser = [[RSSAXParser alloc] initWithDelegate:self]; - - self.itemStack = [NSMutableArray new]; - self.OPMLDocument = [RSOPMLDocument new]; - [self pushItem:self.OPMLDocument]; - - [parser parseData:parserData.data]; - [parser finishParsing]; - } -} - -- (BOOL)canParseData:(NSData *)d { - - // Check for 0, nil); - - /*If itemStack is empty, bad things are happening. - But we still shouldn't crash in production.*/ - - if (self.itemStack.count > 0) { - [self.itemStack removeLastObject]; - } -} - - -- (RSOPMLItem *)currentItem { - - return self.itemStack.lastObject; -} - - -#pragma mark - RSSAXParserDelegate - -static const char *kOutline = "outline"; -static const char kOutlineLength = 8; - -- (void)saxParser:(RSSAXParser *)SAXParser XMLStartElement:(const xmlChar *)localName prefix:(const xmlChar *)prefix uri:(const xmlChar *)uri numberOfNamespaces:(NSInteger)numberOfNamespaces namespaces:(const xmlChar **)namespaces numberOfAttributes:(NSInteger)numberOfAttributes numberDefaulted:(int)numberDefaulted attributes:(const xmlChar **)attributes { - - if (RSSAXEqualTags(localName, kTitle, kTitleLength)) { - [SAXParser beginStoringCharacters]; - return; - } - - if (!RSSAXEqualTags(localName, kOutline, kOutlineLength)) { - return; - } - - RSOPMLItem *item = [RSOPMLItem new]; - item.attributes = [SAXParser attributesDictionary:attributes numberOfAttributes:numberOfAttributes]; - - [[self currentItem] addChild:item]; - [self pushItem:item]; -} - - -- (void)saxParser:(RSSAXParser *)SAXParser XMLEndElement:(const xmlChar *)localName prefix:(const xmlChar *)prefix uri:(const xmlChar *)uri { - - if (RSSAXEqualTags(localName, kTitle, kTitleLength)) { - RSOPMLItem* item = [self currentItem]; - if ([item isKindOfClass:[RSOPMLDocument class]]) { - ((RSOPMLDocument *)item).title = SAXParser.currentStringWithTrimmedWhitespace; - } - return; - } - - if (RSSAXEqualTags(localName, kOutline, kOutlineLength)) { - [self popItem]; - } -} - - -static const char *kText = "text"; -static const NSInteger kTextLength = 5; - -static const char *kTitle = "title"; -static const NSInteger kTitleLength = 6; - -static const char *kDescription = "description"; -static const NSInteger kDescriptionLength = 12; - -static const char *kType = "type"; -static const NSInteger kTypeLength = 5; - -static const char *kVersion = "version"; -static const NSInteger kVersionLength = 8; - -static const char *kHTMLURL = "htmlUrl"; -static const NSInteger kHTMLURLLength = 8; - -static const char *kXMLURL = "xmlUrl"; -static const NSInteger kXMLURLLength = 7; - -- (NSString *)saxParser:(RSSAXParser *)SAXParser internedStringForName:(const xmlChar *)name prefix:(const xmlChar *)prefix { - - if (prefix) { - return nil; - } - - size_t nameLength = strlen((const char *)name); - - if (nameLength == kTextLength - 1) { - if (RSSAXEqualTags(name, kText, kTextLength)) { - return OPMLTextKey; - } - if (RSSAXEqualTags(name, kType, kTypeLength)) { - return OPMLTypeKey; - } - } - - else if (nameLength == kTitleLength - 1) { - if (RSSAXEqualTags(name, kTitle, kTitleLength)) { - return OPMLTitleKey; - } - } - - else if (nameLength == kXMLURLLength - 1) { - if (RSSAXEqualTags(name, kXMLURL, kXMLURLLength)) { - return OPMLXMLURLKey; - } - } - - else if (nameLength == kVersionLength - 1) { - if (RSSAXEqualTags(name, kVersion, kVersionLength)) { - return OPMLVersionKey; - } - if (RSSAXEqualTags(name, kHTMLURL, kHTMLURLLength)) { - return OPMLHMTLURLKey; - } - } - - else if (nameLength == kDescriptionLength - 1) { - if (RSSAXEqualTags(name, kDescription, kDescriptionLength)) { - return OPMLDescriptionKey; - } - } - - return nil; -} - - -static const char *kRSSUppercase = "RSS"; -static const char *kRSSLowercase = "rss"; -static const NSUInteger kRSSLength = 3; -static NSString *RSSUppercaseValue = @"RSS"; -static NSString *RSSLowercaseValue = @"rss"; -static NSString *emptyString = @""; - -static BOOL equalBytes(const void *bytes1, const void *bytes2, NSUInteger length) { - - return memcmp(bytes1, bytes2, length) == 0; -} - -- (NSString *)saxParser:(RSSAXParser *)SAXParser internedStringForValue:(const void *)bytes length:(NSUInteger)length { - - - if (length < 1) { - return emptyString; - } - - if (length == kRSSLength) { - - if (equalBytes(bytes, kRSSUppercase, kRSSLength)) { - return RSSUppercaseValue; - } - else if (equalBytes(bytes, kRSSLowercase, kRSSLength)) { - return RSSLowercaseValue; - } - - } - - return nil; -} - - -@end diff --git a/RSParser/Sources/ObjC/RSParsedArticle.h b/RSParser/Sources/ObjC/RSParsedArticle.h deleted file mode 100755 index a2bfb3175..000000000 --- a/RSParser/Sources/ObjC/RSParsedArticle.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// RSParsedArticle.h -// RSParser -// -// Created by Brent Simmons on 12/6/14. -// Copyright (c) 2014 Ranchero Software LLC. All rights reserved. -// - -@import Foundation; - -@class RSParsedEnclosure; -@class RSParsedAuthor; - -@interface RSParsedArticle : NSObject - -- (nonnull instancetype)initWithFeedURL:(NSString * _Nonnull)feedURL; - -@property (nonatomic, readonly, nonnull) NSString *feedURL; -@property (nonatomic, nonnull) NSString *articleID; //guid, if present, or calculated from other attributes. Should be unique to the feed, but not necessarily unique across different feeds. (Not suitable for a database ID.) - -@property (nonatomic, nullable) NSString *guid; -@property (nonatomic, nullable) NSString *title; -@property (nonatomic, nullable) NSString *body; -@property (nonatomic, nullable) NSString *link; -@property (nonatomic, nullable) NSString *permalink; -@property (nonatomic, nullable) NSSet *authors; -@property (nonatomic, nullable) NSSet *enclosures; -@property (nonatomic, nullable) NSDate *datePublished; -@property (nonatomic, nullable) NSDate *dateModified; -@property (nonatomic, nonnull) NSDate *dateParsed; -@property (nonatomic, nullable) NSString *language; - -- (void)addEnclosure:(RSParsedEnclosure *_Nonnull)enclosure; -- (void)addAuthor:(RSParsedAuthor *_Nonnull)author; - -@end - diff --git a/RSParser/Sources/ObjC/RSParsedArticle.m b/RSParser/Sources/ObjC/RSParsedArticle.m deleted file mode 100755 index b94930a79..000000000 --- a/RSParser/Sources/ObjC/RSParsedArticle.m +++ /dev/null @@ -1,134 +0,0 @@ -// -// RSParsedArticle.m -// RSParser -// -// Created by Brent Simmons on 12/6/14. -// Copyright (c) 2014 Ranchero Software LLC. All rights reserved. -// - - -#import "RSParsedArticle.h" -#import "RSParserInternal.h" -#import "NSString+RSParser.h" -#import "RSParsedAuthor.h" -#import "RSParsedEnclosure.h" - - - -@implementation RSParsedArticle - - -#pragma mark - Init - -- (instancetype)initWithFeedURL:(NSString *)feedURL { - - NSParameterAssert(feedURL != nil); - - self = [super init]; - if (!self) { - return nil; - } - - _feedURL = feedURL; - _dateParsed = [NSDate date]; - - return self; -} - - -#pragma mark - Enclosures - -- (void)addEnclosure:(RSParsedEnclosure *)enclosure { - - if (self.enclosures) { - self.enclosures = [self.enclosures setByAddingObject:enclosure]; - } - else { - self.enclosures = [NSSet setWithObject:enclosure]; - } -} - -#pragma mark - Authors - -- (void)addAuthor:(RSParsedAuthor *)author { - - if (self.authors) { - self.authors = [self.authors setByAddingObject:author]; - } - else { - self.authors = [NSSet setWithObject:author]; - } -} - -#pragma mark - articleID - -- (NSString *)articleID { - - if (self.guid) { - return self.guid; - } - - if (!_articleID) { - _articleID = [self calculatedArticleID]; - } - - return _articleID; -} - - -- (NSString *)calculatedArticleID { - - /*Concatenate a combination of properties when no guid. Then hash the result. - In general, feeds should have guids. When they don't, re-runs are very likely, - because there's no other 100% reliable way to determine identity. - This is intended to create an ID unique inside a feed, but not globally unique. - Not suitable for a database ID, in other words.*/ - - NSMutableString *s = [NSMutableString stringWithString:@""]; - - NSString *datePublishedTimeStampString = nil; - if (self.datePublished) { - datePublishedTimeStampString = [NSString stringWithFormat:@"%.0f", self.datePublished.timeIntervalSince1970]; - } - - // Ideally we have a permalink and a pubDate. Either one would probably be a good guid, but together they should be rock-solid. (In theory. Feeds are buggy, though.) - if (!RSParserStringIsEmpty(self.permalink) && datePublishedTimeStampString) { - [s appendString:self.permalink]; - [s appendString:datePublishedTimeStampString]; - } - - else if (!RSParserStringIsEmpty(self.link) && datePublishedTimeStampString) { - [s appendString:self.link]; - [s appendString:datePublishedTimeStampString]; - } - - else if (!RSParserStringIsEmpty(self.title) && datePublishedTimeStampString) { - [s appendString:self.title]; - [s appendString:datePublishedTimeStampString]; - } - - else if (datePublishedTimeStampString) { - [s appendString:datePublishedTimeStampString]; - } - - else if (!RSParserStringIsEmpty(self.permalink)) { - [s appendString:self.permalink]; - } - - else if (!RSParserStringIsEmpty(self.link)) { - [s appendString:self.link]; - } - - else if (!RSParserStringIsEmpty(self.title)) { - [s appendString:self.title]; - } - - else if (!RSParserStringIsEmpty(self.body)) { - [s appendString:self.body]; - } - - return [s rsparser_md5Hash]; -} - -@end - diff --git a/RSParser/Sources/ObjC/RSParsedAuthor.h b/RSParser/Sources/ObjC/RSParsedAuthor.h deleted file mode 100644 index 2c28236a2..000000000 --- a/RSParser/Sources/ObjC/RSParsedAuthor.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// RSParsedAuthor.h -// RSParserTests -// -// Created by Brent Simmons on 12/19/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -@import Foundation; - -@interface RSParsedAuthor : NSObject - -@property (nonatomic, nullable) NSString *name; -@property (nonatomic, nullable) NSString *emailAddress; -@property (nonatomic, nullable) NSString *url; - -+ (instancetype _Nonnull )authorWithSingleString:(NSString *_Nonnull)s; // Don’t know which property it is. Guess based on contents of the string. Common with RSS. - -@end diff --git a/RSParser/Sources/ObjC/RSParsedAuthor.m b/RSParser/Sources/ObjC/RSParsedAuthor.m deleted file mode 100644 index 154b546c8..000000000 --- a/RSParser/Sources/ObjC/RSParsedAuthor.m +++ /dev/null @@ -1,34 +0,0 @@ -// -// RSParsedAuthor.m -// RSParserTests -// -// Created by Brent Simmons on 12/19/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -#import "NSString+RSParser.h" - -#import "RSParsedAuthor.h" - -@implementation RSParsedAuthor - -+ (instancetype)authorWithSingleString:(NSString *)s { - - // The author element in RSS is supposed to be email address — but often it’s a name, and sometimes a URL. - - RSParsedAuthor *author = [[self alloc] init]; - - if ([s rsparser_contains:@"@"]) { - author.emailAddress = s; - } - else if ([s.lowercaseString hasPrefix:@"http"]) { - author.url = s; - } - else { - author.name = s; - } - - return author; -} - -@end diff --git a/RSParser/Sources/ObjC/RSParsedEnclosure.h b/RSParser/Sources/ObjC/RSParsedEnclosure.h deleted file mode 100644 index 8fc9e404d..000000000 --- a/RSParser/Sources/ObjC/RSParsedEnclosure.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// 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; -@property (nonatomic, nullable) NSString *title; - -@end - -NS_ASSUME_NONNULL_END diff --git a/RSParser/Sources/ObjC/RSParsedEnclosure.m b/RSParser/Sources/ObjC/RSParsedEnclosure.m deleted file mode 100644 index f6f35da59..000000000 --- a/RSParser/Sources/ObjC/RSParsedEnclosure.m +++ /dev/null @@ -1,13 +0,0 @@ -// -// 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/RSParser/Sources/ObjC/RSParsedFeed.h b/RSParser/Sources/ObjC/RSParsedFeed.h deleted file mode 100755 index 80be90fed..000000000 --- a/RSParser/Sources/ObjC/RSParsedFeed.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// RSParsedFeed.h -// RSParser -// -// Created by Brent Simmons on 7/12/15. -// Copyright © 2015 Ranchero Software, LLC. All rights reserved. -// - -@import Foundation; - -@class RSParsedArticle; - -@interface RSParsedFeed : NSObject - -- (nonnull instancetype)initWithURLString:(NSString * _Nonnull)urlString title:(NSString * _Nullable)title link:(NSString * _Nullable)link language:(NSString * _Nullable)language articles:(NSArray * _Nonnull)articles; - -@property (nonatomic, readonly, nonnull) NSString *urlString; -@property (nonatomic, readonly, nullable) NSString *title; -@property (nonatomic, readonly, nullable) NSString *link; -@property (nonatomic, readonly, nullable) NSString *language; -@property (nonatomic, readonly, nonnull) NSSet *articles; - -@end diff --git a/RSParser/Sources/ObjC/RSParsedFeed.m b/RSParser/Sources/ObjC/RSParsedFeed.m deleted file mode 100755 index ef0c42e76..000000000 --- a/RSParser/Sources/ObjC/RSParsedFeed.m +++ /dev/null @@ -1,32 +0,0 @@ -// -// RSParsedFeed.m -// RSParser -// -// Created by Brent Simmons on 7/12/15. -// Copyright © 2015 Ranchero Software, LLC. All rights reserved. -// - -#import "RSParsedFeed.h" - - - -@implementation RSParsedFeed - -- (instancetype)initWithURLString:(NSString *)urlString title:(NSString *)title link:(NSString *)link language:(NSString *)language articles:(NSSet *)articles { - - self = [super init]; - if (!self) { - return nil; - } - - _urlString = urlString; - _title = title; - _link = link; - _language = language; - _articles = articles; - - return self; -} - - -@end diff --git a/RSParser/Sources/ObjC/RSParserInternal.h b/RSParser/Sources/ObjC/RSParserInternal.h deleted file mode 100755 index 76209e076..000000000 --- a/RSParser/Sources/ObjC/RSParserInternal.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// RSParserInternal.h -// RSParser -// -// Created by Brent Simmons on 12/26/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - -@import Foundation; - -NS_ASSUME_NONNULL_BEGIN - -BOOL RSParserObjectIsEmpty(id _Nullable obj); -BOOL RSParserStringIsEmpty(NSString * _Nullable s); - - -@interface NSDictionary (RSParserInternal) - -- (nullable id)rsparser_objectForCaseInsensitiveKey:(NSString *)key; - -@end - -NS_ASSUME_NONNULL_END - diff --git a/RSParser/Sources/ObjC/RSParserInternal.m b/RSParser/Sources/ObjC/RSParserInternal.m deleted file mode 100755 index 4ba6f8a97..000000000 --- a/RSParser/Sources/ObjC/RSParserInternal.m +++ /dev/null @@ -1,61 +0,0 @@ -// -// RSParserInternal.m -// RSParser -// -// Created by Brent Simmons on 12/26/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - - -#import "RSParserInternal.h" -#import - - -static BOOL RSParserIsNil(id obj) { - - return obj == nil || obj == [NSNull null]; -} - -BOOL RSParserObjectIsEmpty(id obj) { - - if (RSParserIsNil(obj)) { - return YES; - } - - if ([obj respondsToSelector:@selector(count)]) { - return [obj count] < 1; - } - - if ([obj respondsToSelector:@selector(length)]) { - return [obj length] < 1; - } - - return NO; /*Shouldn't get here very often.*/ -} - -BOOL RSParserStringIsEmpty(NSString *s) { - - return RSParserIsNil(s) || s.length < 1; -} - - -@implementation NSDictionary (RSParserInternal) - -- (nullable id)rsparser_objectForCaseInsensitiveKey:(NSString *)key { - - id obj = self[key]; - if (obj) { - return obj; - } - - for (NSString *oneKey in self.allKeys) { - - if ([oneKey isKindOfClass:[NSString class]] && [key caseInsensitiveCompare:oneKey] == NSOrderedSame) { - return self[oneKey]; - } - } - - return nil; -} - -@end diff --git a/RSParser/Sources/ObjC/RSRSSParser.h b/RSParser/Sources/ObjC/RSRSSParser.h deleted file mode 100755 index 26e97d0a2..000000000 --- a/RSParser/Sources/ObjC/RSRSSParser.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// RSRSSParser.h -// RSParser -// -// Created by Brent Simmons on 1/6/15. -// Copyright (c) 2015 Ranchero Software LLC. All rights reserved. -// - -@import Foundation; - -@class ParserData; -@class RSParsedFeed; - -@interface RSRSSParser : NSObject - -+ (RSParsedFeed *)parseFeedWithData:(ParserData *)parserData; - - -@end diff --git a/RSParser/Sources/ObjC/RSRSSParser.m b/RSParser/Sources/ObjC/RSRSSParser.m deleted file mode 100755 index 455320ab2..000000000 --- a/RSParser/Sources/ObjC/RSRSSParser.m +++ /dev/null @@ -1,523 +0,0 @@ -// -// RSRSSParser.m -// RSParser -// -// Created by Brent Simmons on 1/6/15. -// Copyright (c) 2015 Ranchero Software LLC. All rights reserved. -// - -#import "RSRSSParser.h" -#import "RSSAXParser.h" -#import "RSParsedFeed.h" -#import "RSParsedArticle.h" -#import "RSParserInternal.h" -#import "NSString+RSParser.h" -#import "RSDateParser.h" -#import "ParserData.h" -#import "RSParsedEnclosure.h" -#import "RSParsedAuthor.h" - - - -#import - - -@interface RSRSSParser () - -@property (nonatomic) NSData *feedData; -@property (nonatomic) NSString *urlString; -@property (nonatomic) NSDictionary *currentAttributes; -@property (nonatomic) RSSAXParser *parser; -@property (nonatomic) NSMutableArray *articles; -@property (nonatomic) BOOL parsingArticle; -@property (nonatomic) BOOL parsingAuthor; -@property (nonatomic, readonly) RSParsedArticle *currentArticle; -@property (nonatomic) BOOL parsingChannelImage; -@property (nonatomic, readonly) NSDate *currentDate; -@property (nonatomic) BOOL endRSSFound; -@property (nonatomic) NSString *link; -@property (nonatomic) NSString *title; -@property (nonatomic) NSDate *dateParsed; -@property (nonatomic) BOOL isRDF; -@property (nonatomic) NSString *language; - -@end - - -@implementation RSRSSParser - -#pragma mark - Class Methods - -+ (RSParsedFeed *)parseFeedWithData:(ParserData *)parserData { - - RSRSSParser *parser = [[[self class] alloc] initWithParserData:parserData]; - return [parser parseFeed]; -} - -#pragma mark - Init - -- (instancetype)initWithParserData:(ParserData *)parserData { - - self = [super init]; - if (!self) { - return nil; - } - - _feedData = parserData.data; - _urlString = parserData.url; - _parser = [[RSSAXParser alloc] initWithDelegate:self]; - _articles = [NSMutableArray new]; - - return self; -} - -#pragma mark - API - -- (RSParsedFeed *)parseFeed { - - [self parse]; - - RSParsedFeed *parsedFeed = [[RSParsedFeed alloc] initWithURLString:self.urlString title:self.title link:self.link language:self.language articles:self.articles]; - - return parsedFeed; -} - - -#pragma mark - Constants - -static NSString *kIsPermaLinkKey = @"isPermaLink"; -static NSString *kURLKey = @"url"; -static NSString *kLengthKey = @"length"; -static NSString *kTypeKey = @"type"; -static NSString *kFalseValue = @"false"; -static NSString *kTrueValue = @"true"; -static NSString *kContentEncodedKey = @"content:encoded"; -static NSString *kDCDateKey = @"dc:date"; -static NSString *kDCCreatorKey = @"dc:creator"; -static NSString *kRDFAboutKey = @"rdf:about"; - -static const char *kItem = "item"; -static const NSInteger kItemLength = 5; - -static const char *kImage = "image"; -static const NSInteger kImageLength = 6; - -static const char *kLink = "link"; -static const NSInteger kLinkLength = 5; - -static const char *kTitle = "title"; -static const NSInteger kTitleLength = 6; - -static const char *kDC = "dc"; -static const NSInteger kDCLength = 3; - -static const char *kCreator = "creator"; -static const NSInteger kCreatorLength = 8; - -static const char *kDate = "date"; -static const NSInteger kDateLength = 5; - -static const char *kContent = "content"; -static const NSInteger kContentLength = 8; - -static const char *kEncoded = "encoded"; -static const NSInteger kEncodedLength = 8; - -static const char *kGuid = "guid"; -static const NSInteger kGuidLength = 5; - -static const char *kPubDate = "pubDate"; -static const NSInteger kPubDateLength = 8; - -static const char *kAuthor = "author"; -static const NSInteger kAuthorLength = 7; - -static const char *kDescription = "description"; -static const NSInteger kDescriptionLength = 12; - -static const char *kRSS = "rss"; -static const NSInteger kRSSLength = 4; - -static const char *kURL = "url"; -static const NSInteger kURLLength = 4; - -static const char *kLength = "length"; -static const NSInteger kLengthLength = 7; - -static const char *kType = "type"; -static const NSInteger kTypeLength = 5; - -static const char *kIsPermaLink = "isPermaLink"; -static const NSInteger kIsPermaLinkLength = 12; - -static const char *kRDF = "rdf"; -static const NSInteger kRDFlength = 4; - -static const char *kAbout = "about"; -static const NSInteger kAboutLength = 6; - -static const char *kFalse = "false"; -static const NSInteger kFalseLength = 6; - -static const char *kTrue = "true"; -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; - -static const char *kLanguage = "language"; -static const NSInteger kLanguageLength = 9; - -#pragma mark - Parsing - -- (void)parse { - - self.dateParsed = [NSDate date]; - - @autoreleasepool { - [self.parser parseData:self.feedData]; - [self.parser finishParsing]; - } -} - - -- (void)addArticle { - - RSParsedArticle *article = [[RSParsedArticle alloc] initWithFeedURL:self.urlString]; - article.dateParsed = self.dateParsed; - - [self.articles addObject:article]; -} - - -- (RSParsedArticle *)currentArticle { - - return self.articles.lastObject; -} - - -- (void)addFeedElement:(const xmlChar *)localName prefix:(const xmlChar *)prefix { - - if (prefix != NULL) { - return; - } - - if (RSSAXEqualTags(localName, kLink, kLinkLength)) { - if (!self.link) { - self.link = [self currentString]; - } - } - - else if (RSSAXEqualTags(localName, kTitle, kTitleLength)) { - self.title = [self currentString]; - } - - else if (RSSAXEqualTags(localName, kLanguage, kLanguageLength)) { - self.language = [self currentString]; - } -} - -- (void)addAuthorWithString:(NSString *)authorString { - - if (RSParserStringIsEmpty(authorString)) { - return; - } - - RSParsedAuthor *author = [RSParsedAuthor authorWithSingleString:[self currentString]]; - [self.currentArticle addAuthor:author]; -} - -- (void)addDCElement:(const xmlChar *)localName { - - if (RSSAXEqualTags(localName, kCreator, kCreatorLength)) { - [self addAuthorWithString:[self currentString]]; - } - else if (RSSAXEqualTags(localName, kDate, kDateLength)) { - self.currentArticle.datePublished = self.currentDate; - } -} - - -- (void)addGuid { - - NSString *guid = [self currentString]; - self.currentArticle.guid = guid; - - NSString *isPermaLinkValue = [self.currentAttributes rsparser_objectForCaseInsensitiveKey:@"ispermalink"]; - if (!isPermaLinkValue || ![isPermaLinkValue isEqualToString:@"false"]) { - if ([self stringIsProbablyAURLOrRelativePath:guid]) { - self.currentArticle.permalink = [self urlString:guid]; - } - } -} - -- (void)addEnclosure { - - NSDictionary *attributes = self.currentAttributes; - NSString *url = attributes[kURLKey]; - if (!url || url.length < 1) { - return; - } - - RSParsedEnclosure *enclosure = [[RSParsedEnclosure alloc] init]; - enclosure.url = url; - enclosure.length = [attributes[kLengthKey] integerValue]; - enclosure.mimeType = attributes[kTypeKey]; - - [self.currentArticle addEnclosure:enclosure]; -} - -- (BOOL)stringIsProbablyAURLOrRelativePath:(NSString *)s { - - /*The RSS guid is defined as a permalink, except when it appears like this: - some—identifier - However, people often seem to think it’s *not* a permalink by default, even - though it is. So we try to detect the situation where the value is not a URL string, - and not even a relative path. This may need to evolve over time as we find - feeds broken in different ways.*/ - - if (![s rsparser_contains:@"/"]) { - // This seems to be just about the best possible check. - // Bad guids are often just integers, for instance. - return NO; - } - - if ([s.lowercaseString hasPrefix:@"tag:"]) { // A common non-URL guid form - return NO; - } - return YES; -} - -- (NSString *)urlString:(NSString *)s { - - /*Resolve against home page URL (if available) or feed URL.*/ - - if ([[s lowercaseString] hasPrefix:@"http"]) { - return s; - } - - if (!self.link) { - //TODO: get feed URL and use that to resolve URL.*/ - return s; - } - - NSURL *baseURL = [NSURL URLWithString:self.link]; - if (!baseURL) { - return s; - } - - NSURL *resolvedURL = [NSURL URLWithString:s relativeToURL:baseURL]; - if (resolvedURL.absoluteString) { - return resolvedURL.absoluteString; - } - - return s; -} - - -- (NSString *)currentString { - - return self.parser.currentStringWithTrimmedWhitespace; -} - - -- (void)addArticleElement:(const xmlChar *)localName prefix:(const xmlChar *)prefix { - - if (RSSAXEqualTags(prefix, kDC, kDCLength)) { - - [self addDCElement:localName]; - return; - } - - if (RSSAXEqualTags(prefix, kContent, kContentLength) && RSSAXEqualTags(localName, kEncoded, kEncodedLength)) { - NSString *s = [self currentString]; - if (!RSParserStringIsEmpty(s)) { - self.currentArticle.body = s; - } - return; - } - - if (prefix != NULL) { - return; - } - - if (RSSAXEqualTags(localName, kGuid, kGuidLength)) { - [self addGuid]; - } - else if (RSSAXEqualTags(localName, kPubDate, kPubDateLength)) { - self.currentArticle.datePublished = self.currentDate; - } - else if (RSSAXEqualTags(localName, kAuthor, kAuthorLength)) { - [self addAuthorWithString:[self currentString]]; - } - else if (RSSAXEqualTags(localName, kLink, kLinkLength)) { - self.currentArticle.link = [self urlString:[self currentString]]; - } - else if (RSSAXEqualTags(localName, kDescription, kDescriptionLength)) { - - if (!self.currentArticle.body) { - self.currentArticle.body = [self currentString]; - } - } - else if (!self.parsingAuthor && RSSAXEqualTags(localName, kTitle, kTitleLength)) { - NSString *articleTitle = [self currentString]; - if (articleTitle != nil) { - self.currentArticle.title = articleTitle; - } - } - else if (RSSAXEqualTags(localName, kEnclosure, kEnclosureLength)) { - [self addEnclosure]; - } -} - - -- (NSDate *)currentDate { - - return RSDateWithBytes(self.parser.currentCharacters.bytes, self.parser.currentCharacters.length); -} - - -#pragma mark - RSSAXParserDelegate - -- (void)saxParser:(RSSAXParser *)SAXParser XMLStartElement:(const xmlChar *)localName prefix:(const xmlChar *)prefix uri:(const xmlChar *)uri numberOfNamespaces:(NSInteger)numberOfNamespaces namespaces:(const xmlChar **)namespaces numberOfAttributes:(NSInteger)numberOfAttributes numberDefaulted:(int)numberDefaulted attributes:(const xmlChar **)attributes { - - if (self.endRSSFound) { - return; - } - - if (RSSAXEqualTags(localName, kUppercaseRDF, kUppercaseRDFLength)) { - self.isRDF = YES; - return; - } - - NSDictionary *xmlAttributes = nil; - 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) { - self.currentAttributes = xmlAttributes; - } - - if (!prefix && RSSAXEqualTags(localName, kItem, kItemLength)) { - - [self addArticle]; - self.parsingArticle = YES; - - if (self.isRDF && xmlAttributes && xmlAttributes[kRDFAboutKey]) { /*RSS 1.0 guid*/ - self.currentArticle.guid = xmlAttributes[kRDFAboutKey]; - self.currentArticle.permalink = self.currentArticle.guid; - } - } - - else if (!prefix && RSSAXEqualTags(localName, kImage, kImageLength)) { - self.parsingChannelImage = YES; - } - else if (!prefix && RSSAXEqualTags(localName, kAuthor, kAuthorLength)) { - if (self.parsingArticle) { - self.parsingAuthor = true; - } - } - - if (!self.parsingChannelImage) { - [self.parser beginStoringCharacters]; - } -} - - -- (void)saxParser:(RSSAXParser *)SAXParser XMLEndElement:(const xmlChar *)localName prefix:(const xmlChar *)prefix uri:(const xmlChar *)uri { - - if (self.endRSSFound) { - return; - } - - if (self.isRDF && RSSAXEqualTags(localName, kUppercaseRDF, kUppercaseRDFLength)) { - self.endRSSFound = YES; - } - - else if (RSSAXEqualTags(localName, kRSS, kRSSLength)) { - self.endRSSFound = YES; - } - - else if (RSSAXEqualTags(localName, kImage, kImageLength)) { - self.parsingChannelImage = NO; - } - - else if (RSSAXEqualTags(localName, kItem, kItemLength)) { - self.parsingArticle = NO; - } - - else if (self.parsingArticle) { - [self addArticleElement:localName prefix:prefix]; - if (RSSAXEqualTags(localName, kAuthor, kAuthorLength)) { - self.parsingAuthor = NO; - } - } - - else if (!self.parsingChannelImage) { - [self addFeedElement:localName prefix:prefix]; - } -} - - -- (NSString *)saxParser:(RSSAXParser *)SAXParser internedStringForName:(const xmlChar *)name prefix:(const xmlChar *)prefix { - - if (RSSAXEqualTags(prefix, kRDF, kRDFlength)) { - - if (RSSAXEqualTags(name, kAbout, kAboutLength)) { - return kRDFAboutKey; - } - - return nil; - } - - if (prefix) { - return nil; - } - - if (RSSAXEqualTags(name, kIsPermaLink, kIsPermaLinkLength)) { - return kIsPermaLinkKey; - } - - if (RSSAXEqualTags(name, kURL, kURLLength)) { - return kURLKey; - } - - if (RSSAXEqualTags(name, kLength, kLengthLength)) { - return kLengthKey; - } - - if (RSSAXEqualTags(name, kType, kTypeLength)) { - return kTypeKey; - } - - return nil; -} - - -static BOOL equalBytes(const void *bytes1, const void *bytes2, NSUInteger length) { - - return memcmp(bytes1, bytes2, length) == 0; -} - - -- (NSString *)saxParser:(RSSAXParser *)SAXParser internedStringForValue:(const void *)bytes length:(NSUInteger)length { - - static const NSUInteger falseLength = kFalseLength - 1; - static const NSUInteger trueLength = kTrueLength - 1; - - if (length == falseLength && equalBytes(bytes, kFalse, falseLength)) { - return kFalseValue; - } - - if (length == trueLength && equalBytes(bytes, kTrue, trueLength)) { - return kTrueValue; - } - - return nil; -} - - -@end diff --git a/RSParser/Sources/ObjC/RSSAXHTMLParser.h b/RSParser/Sources/ObjC/RSSAXHTMLParser.h deleted file mode 100755 index f67d60cf6..000000000 --- a/RSParser/Sources/ObjC/RSSAXHTMLParser.h +++ /dev/null @@ -1,55 +0,0 @@ -// -// RSSAXHTMLParser.h -// RSParser -// -// Created by Brent Simmons on 3/6/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - -@import Foundation; - -NS_ASSUME_NONNULL_BEGIN - -@class RSSAXHTMLParser; - -@protocol RSSAXHTMLParserDelegate - -@optional - -- (void)saxParser:(RSSAXHTMLParser *)SAXParser XMLStartElement:(const unsigned char *)localName attributes:(const unsigned char *_Nullable*_Nullable)attributes; - -- (void)saxParser:(RSSAXHTMLParser *)SAXParser XMLEndElement:(nullable const unsigned char *)localName; - -// Length is guaranteed to be greater than 0. -- (void)saxParser:(RSSAXHTMLParser *)SAXParser XMLCharactersFound:(nullable const unsigned char *)characters length:(NSUInteger)length; - -- (void)saxParserDidReachEndOfDocument:(RSSAXHTMLParser *)SAXParser; // If canceled, may not get called (but might). - -@end - - -@interface RSSAXHTMLParser : NSObject - - -- (instancetype)initWithDelegate:(id)delegate; - -- (void)parseData:(NSData *)data; -- (void)parseBytes:(const void *)bytes numberOfBytes:(NSUInteger)numberOfBytes; -- (void)finishParsing; -- (void)cancel; - -@property (nullable, nonatomic, strong, readonly) NSData *currentCharacters; // nil if not storing characters. UTF-8 encoded. -@property (nullable, nonatomic, strong, readonly) NSString *currentString; // Convenience to get string version of currentCharacters. -@property (nullable, nonatomic, strong, readonly) NSString *currentStringWithTrimmedWhitespace; - -- (void)beginStoringCharacters; // Delegate can call from XMLStartElement. Characters will be available in XMLEndElement as currentCharacters property. Storing characters is stopped after each XMLEndElement. - -// Delegate can call from within XMLStartElement. - -- (nullable NSDictionary *)attributesDictionary:(const unsigned char *_Nullable*_Nullable)attributes; - - -@end - -NS_ASSUME_NONNULL_END - diff --git a/RSParser/Sources/ObjC/RSSAXHTMLParser.m b/RSParser/Sources/ObjC/RSSAXHTMLParser.m deleted file mode 100755 index 5df2d84fd..000000000 --- a/RSParser/Sources/ObjC/RSSAXHTMLParser.m +++ /dev/null @@ -1,321 +0,0 @@ -// -// RSSAXHTMLParser.m -// RSParser -// -// Created by Brent Simmons on 3/6/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - -#import "RSSAXHTMLParser.h" -#import "RSSAXParser.h" -#import "RSParserInternal.h" - -#import -#import -#import - - - -@interface RSSAXHTMLParser () - -@property (nonatomic) id delegate; -@property (nonatomic, assign) htmlParserCtxtPtr context; -@property (nonatomic, assign) BOOL storingCharacters; -@property (nonatomic) NSMutableData *characters; -@property (nonatomic) BOOL delegateRespondsToStartElementMethod; -@property (nonatomic) BOOL delegateRespondsToEndElementMethod; -@property (nonatomic) BOOL delegateRespondsToCharactersFoundMethod; -@property (nonatomic) BOOL delegateRespondsToEndOfDocumentMethod; - -@end - - -@implementation RSSAXHTMLParser - - -+ (void)initialize { - - RSSAXInitLibXMLParser(); -} - - -#pragma mark - Init - -- (instancetype)initWithDelegate:(id)delegate { - - self = [super init]; - if (self == nil) - return nil; - - _delegate = delegate; - - if ([_delegate respondsToSelector:@selector(saxParser:XMLStartElement:attributes:)]) { - _delegateRespondsToStartElementMethod = YES; - } - if ([_delegate respondsToSelector:@selector(saxParser:XMLEndElement:)]) { - _delegateRespondsToEndElementMethod = YES; - } - if ([_delegate respondsToSelector:@selector(saxParser:XMLCharactersFound:length:)]) { - _delegateRespondsToCharactersFoundMethod = YES; - } - if ([_delegate respondsToSelector:@selector(saxParserDidReachEndOfDocument:)]) { - _delegateRespondsToEndOfDocumentMethod = YES; - } - - return self; -} - - -#pragma mark - Dealloc - -- (void)dealloc { - - if (_context != nil) { - htmlFreeParserCtxt(_context); - _context = nil; - } - _delegate = nil; -} - - -#pragma mark - API - -static xmlSAXHandler saxHandlerStruct; - -- (void)parseData:(NSData *)data { - - [self parseBytes:data.bytes numberOfBytes:data.length]; -} - - -- (void)parseBytes:(const void *)bytes numberOfBytes:(NSUInteger)numberOfBytes { - - if (self.context == nil) { - - xmlCharEncoding characterEncoding = xmlDetectCharEncoding(bytes, (int)numberOfBytes); - self.context = htmlCreatePushParserCtxt(&saxHandlerStruct, (__bridge void *)self, nil, 0, nil, characterEncoding); - htmlCtxtUseOptions(self.context, XML_PARSE_RECOVER | XML_PARSE_NONET | HTML_PARSE_COMPACT); - } - - @autoreleasepool { - htmlParseChunk(self.context, (const char *)bytes, (int)numberOfBytes, 0); - } -} - - -- (void)finishParsing { - - NSAssert(self.context != nil, nil); - if (self.context == nil) - return; - - @autoreleasepool { - htmlParseChunk(self.context, nil, 0, 1); - htmlFreeParserCtxt(self.context); - self.context = nil; - self.characters = nil; - } -} - - -- (void)cancel { - - @autoreleasepool { - xmlStopParser(self.context); - } -} - - - -- (void)beginStoringCharacters { - self.storingCharacters = YES; - self.characters = [NSMutableData new]; -} - - -- (void)endStoringCharacters { - self.storingCharacters = NO; - self.characters = nil; -} - - -- (NSData *)currentCharacters { - - if (!self.storingCharacters) { - return nil; - } - - return self.characters; -} - - -- (NSString *)currentString { - - NSData *d = self.currentCharacters; - if (RSParserObjectIsEmpty(d)) { - return nil; - } - - return [[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding]; -} - - -- (NSString *)currentStringWithTrimmedWhitespace { - - return [self.currentString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; -} - - -#pragma mark - Attributes Dictionary - -- (NSDictionary *)attributesDictionary:(const xmlChar **)attributes { - - if (!attributes) { - return nil; - } - - NSMutableDictionary *d = [NSMutableDictionary new]; - - NSInteger ix = 0; - NSString *currentKey = nil; - while (true) { - - const xmlChar *oneAttribute = attributes[ix]; - ix++; - - if (!currentKey && !oneAttribute) { - break; - } - - if (!currentKey) { - currentKey = [NSString stringWithUTF8String:(const char *)oneAttribute]; - } - else { - NSString *value = nil; - if (oneAttribute) { - value = [NSString stringWithUTF8String:(const char *)oneAttribute]; - } - - d[currentKey] = value ? value : @""; - currentKey = nil; - } - } - - return [d copy]; -} - - -#pragma mark - Callbacks - -- (void)xmlEndDocument { - - @autoreleasepool { - if (self.delegateRespondsToEndOfDocumentMethod) { - [self.delegate saxParserDidReachEndOfDocument:self]; - } - - [self endStoringCharacters]; - } -} - - -- (void)xmlCharactersFound:(const xmlChar *)ch length:(NSUInteger)length { - - if (length < 1) { - return; - } - - @autoreleasepool { - if (self.storingCharacters) { - [self.characters appendBytes:(const void *)ch length:length]; - } - - if (self.delegateRespondsToCharactersFoundMethod) { - [self.delegate saxParser:self XMLCharactersFound:ch length:length]; - } - } -} - - -- (void)xmlStartElement:(const xmlChar *)localName attributes:(const xmlChar **)attributes { - - @autoreleasepool { - if (self.delegateRespondsToStartElementMethod) { - - [self.delegate saxParser:self XMLStartElement:localName attributes:attributes]; - } - } -} - - -- (void)xmlEndElement:(const xmlChar *)localName { - - @autoreleasepool { - if (self.delegateRespondsToEndElementMethod) { - [self.delegate saxParser:self XMLEndElement:localName]; - } - - [self endStoringCharacters]; - } -} - - -@end - - -static void startElementSAX(void *context, const xmlChar *localname, const xmlChar **attributes) { - - [(__bridge RSSAXHTMLParser *)context xmlStartElement:localname attributes:attributes]; -} - - -static void endElementSAX(void *context, const xmlChar *localname) { - [(__bridge RSSAXHTMLParser *)context xmlEndElement:localname]; -} - - -static void charactersFoundSAX(void *context, const xmlChar *ch, int len) { - [(__bridge RSSAXHTMLParser *)context xmlCharactersFound:ch length:(NSUInteger)len]; -} - - -static void endDocumentSAX(void *context) { - [(__bridge RSSAXHTMLParser *)context xmlEndDocument]; -} - - -static htmlSAXHandler saxHandlerStruct = { - nil, /* internalSubset */ - nil, /* isStandalone */ - nil, /* hasInternalSubset */ - nil, /* hasExternalSubset */ - nil, /* resolveEntity */ - nil, /* getEntity */ - nil, /* entityDecl */ - nil, /* notationDecl */ - nil, /* attributeDecl */ - nil, /* elementDecl */ - nil, /* unparsedEntityDecl */ - nil, /* setDocumentLocator */ - nil, /* startDocument */ - endDocumentSAX, /* endDocument */ - startElementSAX, /* startElement*/ - endElementSAX, /* endElement */ - nil, /* reference */ - charactersFoundSAX, /* characters */ - nil, /* ignorableWhitespace */ - nil, /* processingInstruction */ - nil, /* comment */ - nil, /* warning */ - nil, /* error */ - nil, /* fatalError //: unused error() get all the errors */ - nil, /* getParameterEntity */ - nil, /* cdataBlock */ - nil, /* externalSubset */ - XML_SAX2_MAGIC, - nil, - nil, /* startElementNs */ - nil, /* endElementNs */ - nil /* serror */ -}; - diff --git a/RSParser/Sources/ObjC/RSSAXParser.h b/RSParser/Sources/ObjC/RSSAXParser.h deleted file mode 100755 index 80ca30a75..000000000 --- a/RSParser/Sources/ObjC/RSSAXParser.h +++ /dev/null @@ -1,69 +0,0 @@ -// -// RSSAXParser.h -// RSParser -// -// Created by Brent Simmons on 3/25/15. -// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved. -// - -@import Foundation; - -/*Thread-safe, not re-entrant. - - Calls to the delegate will happen on the same thread where the parser runs. - - This is a low-level streaming XML parser, a thin wrapper for libxml2's SAX parser. It doesn't do much Foundation-ifying quite on purpose -- because the goal is performance and low memory use. - - This class is not meant to be sub-classed. Use the delegate methods. - */ - - -@class RSSAXParser; - -@protocol RSSAXParserDelegate - -@optional - -- (void)saxParser:(RSSAXParser *)SAXParser XMLStartElement:(const unsigned char *)localName prefix:(const unsigned char *)prefix uri:(const unsigned char *)uri numberOfNamespaces:(NSInteger)numberOfNamespaces namespaces:(const unsigned char **)namespaces numberOfAttributes:(NSInteger)numberOfAttributes numberDefaulted:(int)numberDefaulted attributes:(const unsigned char **)attributes; - -- (void)saxParser:(RSSAXParser *)SAXParser XMLEndElement:(const unsigned char *)localName prefix:(const unsigned char *)prefix uri:(const unsigned char *)uri; - -// Length is guaranteed to be greater than 0. -- (void)saxParser:(RSSAXParser *)SAXParser XMLCharactersFound:(const unsigned char *)characters length:(NSUInteger)length; - -- (void)saxParserDidReachEndOfDocument:(RSSAXParser *)SAXParser; /*If canceled, may not get called (but might).*/ - -- (NSString *)saxParser:(RSSAXParser *)SAXParser internedStringForName:(const unsigned char *)name prefix:(const unsigned char *)prefix; /*Okay to return nil. Prefix may be nil.*/ - -- (NSString *)saxParser:(RSSAXParser *)SAXParser internedStringForValue:(const void *)bytes length:(NSUInteger)length; - -@end - - -void RSSAXInitLibXMLParser(void); // Needed by RSSAXHTMLParser. - -/*For use by delegate.*/ - -BOOL RSSAXEqualTags(const unsigned char *localName, const char *tag, NSInteger tagLength); - - -@interface RSSAXParser : NSObject - -- (instancetype)initWithDelegate:(id)delegate; - -- (void)parseData:(NSData *)data; -- (void)parseBytes:(const void *)bytes numberOfBytes:(NSUInteger)numberOfBytes; -- (void)finishParsing; -- (void)cancel; - -@property (nonatomic, strong, readonly) NSData *currentCharacters; /*nil if not storing characters. UTF-8 encoded.*/ -@property (nonatomic, strong, readonly) NSString *currentString; /*Convenience to get string version of currentCharacters.*/ -@property (nonatomic, strong, readonly) NSString *currentStringWithTrimmedWhitespace; - -- (void)beginStoringCharacters; /*Delegate can call from XMLStartElement. Characters will be available in XMLEndElement as currentCharacters property. Storing characters is stopped after each XMLEndElement.*/ - -/*Delegate can call from within XMLStartElement. Returns nil if numberOfAttributes < 1.*/ - -- (NSDictionary *)attributesDictionary:(const unsigned char **)attributes numberOfAttributes:(NSInteger)numberOfAttributes; - -@end diff --git a/RSParser/Sources/ObjC/RSSAXParser.m b/RSParser/Sources/ObjC/RSSAXParser.m deleted file mode 100755 index 02d6988b8..000000000 --- a/RSParser/Sources/ObjC/RSSAXParser.m +++ /dev/null @@ -1,353 +0,0 @@ -// -// RSSAXParser.m -// RSParser -// -// Created by Brent Simmons on 3/25/15. -// Copyright (c) 2015 Ranchero Software, LLC. All rights reserved. -// - -#import "RSSAXParser.h" -#import "RSParserInternal.h" - -#import -#import -#import - - - -@interface RSSAXParser () - -@property (nonatomic, weak) id delegate; -@property (nonatomic, assign) xmlParserCtxtPtr context; -@property (nonatomic, assign) BOOL storingCharacters; -@property (nonatomic) NSMutableData *characters; -@property (nonatomic) BOOL delegateRespondsToInternedStringMethod; -@property (nonatomic) BOOL delegateRespondsToInternedStringForValueMethod; -@property (nonatomic) BOOL delegateRespondsToStartElementMethod; -@property (nonatomic) BOOL delegateRespondsToEndElementMethod; -@property (nonatomic) BOOL delegateRespondsToCharactersFoundMethod; -@property (nonatomic) BOOL delegateRespondsToEndOfDocumentMethod; - -@end - - -@implementation RSSAXParser - -+ (void)initialize { - - RSSAXInitLibXMLParser(); -} - - -#pragma mark - Init - -- (instancetype)initWithDelegate:(id)delegate { - - self = [super init]; - if (self == nil) - return nil; - - _delegate = delegate; - - if ([_delegate respondsToSelector:@selector(saxParser:internedStringForName:prefix:)]) { - _delegateRespondsToInternedStringMethod = YES; - } - if ([_delegate respondsToSelector:@selector(saxParser:internedStringForValue:length:)]) { - _delegateRespondsToInternedStringForValueMethod = YES; - } - if ([_delegate respondsToSelector:@selector(saxParser:XMLStartElement:prefix:uri:numberOfNamespaces:namespaces:numberOfAttributes:numberDefaulted:attributes:)]) { - _delegateRespondsToStartElementMethod = YES; - } - if ([_delegate respondsToSelector:@selector(saxParser:XMLEndElement:prefix:uri:)]) { - _delegateRespondsToEndElementMethod = YES; - } - if ([_delegate respondsToSelector:@selector(saxParser:XMLCharactersFound:length:)]) { - _delegateRespondsToCharactersFoundMethod = YES; - } - if ([_delegate respondsToSelector:@selector(saxParserDidReachEndOfDocument:)]) { - _delegateRespondsToEndOfDocumentMethod = YES; - } - - return self; -} - - -#pragma mark - Dealloc - -- (void)dealloc { - if (_context != nil) { - xmlFreeParserCtxt(_context); - _context = nil; - } - _delegate = nil; -} - - -#pragma mark - API - -static xmlSAXHandler saxHandlerStruct; - -- (void)parseData:(NSData *)data { - - [self parseBytes:data.bytes numberOfBytes:data.length]; -} - - -- (void)parseBytes:(const void *)bytes numberOfBytes:(NSUInteger)numberOfBytes { - - if (self.context == nil) { - - self.context = xmlCreatePushParserCtxt(&saxHandlerStruct, (__bridge void *)self, nil, 0, nil); - xmlCtxtUseOptions(self.context, XML_PARSE_RECOVER | XML_PARSE_NOENT); - } - - @autoreleasepool { - xmlParseChunk(self.context, (const char *)bytes, (int)numberOfBytes, 0); - } -} - - -- (void)finishParsing { - - NSAssert(self.context != nil, nil); - if (self.context == nil) - return; - - @autoreleasepool { - xmlParseChunk(self.context, nil, 0, 1); - xmlFreeParserCtxt(self.context); - self.context = nil; - self.characters = nil; - } -} - - -- (void)cancel { - - @autoreleasepool { - xmlStopParser(self.context); - } -} - - -- (void)beginStoringCharacters { - self.storingCharacters = YES; - self.characters = [NSMutableData new]; -} - - -- (void)endStoringCharacters { - self.storingCharacters = NO; - self.characters = nil; -} - - -- (NSData *)currentCharacters { - - if (!self.storingCharacters) { - return nil; - } - - return self.characters; -} - - -- (NSString *)currentString { - - NSData *d = self.currentCharacters; - if (RSParserObjectIsEmpty(d)) { - return nil; - } - - return [[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding]; -} - - -- (NSString *)currentStringWithTrimmedWhitespace { - - return [self.currentString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; -} - - -#pragma mark - Attributes Dictionary - -- (NSDictionary *)attributesDictionary:(const xmlChar **)attributes numberOfAttributes:(NSInteger)numberOfAttributes { - - if (numberOfAttributes < 1 || !attributes) { - return nil; - } - - NSMutableDictionary *d = [NSMutableDictionary new]; - - @autoreleasepool { - NSInteger i = 0, j = 0; - for (i = 0, j = 0; i < numberOfAttributes; i++, j+=5) { - - NSUInteger lenValue = (NSUInteger)(attributes[j + 4] - attributes[j + 3]); - NSString *value = nil; - - if (self.delegateRespondsToInternedStringForValueMethod) { - value = [self.delegate saxParser:self internedStringForValue:(const void *)attributes[j + 3] length:lenValue]; - } - if (!value) { - value = [[NSString alloc] initWithBytes:(const void *)attributes[j + 3] length:lenValue encoding:NSUTF8StringEncoding]; - } - - NSString *attributeName = nil; - - if (self.delegateRespondsToInternedStringMethod) { - attributeName = [self.delegate saxParser:self internedStringForName:(const xmlChar *)attributes[j] prefix:(const xmlChar *)attributes[j + 1]]; - } - - if (!attributeName) { - attributeName = [NSString stringWithUTF8String:(const char *)attributes[j]]; - if (attributes[j + 1]) { - NSString *attributePrefix = [NSString stringWithUTF8String:(const char *)attributes[j + 1]]; - attributeName = [NSString stringWithFormat:@"%@:%@", attributePrefix, attributeName]; - } - } - - if (value && attributeName) { - d[attributeName] = value; - } - } - } - - return d; -} - - -#pragma mark - Equal Tags - -BOOL RSSAXEqualTags(const xmlChar *localName, const char *tag, NSInteger tagLength) { - - if (!localName) { - return NO; - } - return !strncmp((const char *)localName, tag, (size_t)tagLength); -} - - -#pragma mark - Callbacks - -- (void)xmlEndDocument { - - @autoreleasepool { - if (self.delegateRespondsToEndOfDocumentMethod) { - [self.delegate saxParserDidReachEndOfDocument:self]; - } - - [self endStoringCharacters]; - } -} - - -- (void)xmlCharactersFound:(const xmlChar *)ch length:(NSUInteger)length { - - if (length < 1) { - return; - } - - @autoreleasepool { - if (self.storingCharacters) { - [self.characters appendBytes:(const void *)ch length:length]; - } - - if (self.delegateRespondsToCharactersFoundMethod) { - [self.delegate saxParser:self XMLCharactersFound:ch length:length]; - } - } -} - - -- (void)xmlStartElement:(const xmlChar *)localName prefix:(const xmlChar *)prefix uri:(const xmlChar *)uri numberOfNamespaces:(int)numberOfNamespaces namespaces:(const xmlChar **)namespaces numberOfAttributes:(int)numberOfAttributes numberDefaulted:(int)numberDefaulted attributes:(const xmlChar **)attributes { - - @autoreleasepool { - if (self.delegateRespondsToStartElementMethod) { - - [self.delegate saxParser:self XMLStartElement:localName prefix:prefix uri:uri numberOfNamespaces:numberOfNamespaces namespaces:namespaces numberOfAttributes:numberOfAttributes numberDefaulted:numberDefaulted attributes:attributes]; - } - } -} - - -- (void)xmlEndElement:(const xmlChar *)localName prefix:(const xmlChar *)prefix uri:(const xmlChar *)uri { - - @autoreleasepool { - if (self.delegateRespondsToEndElementMethod) { - [self.delegate saxParser:self XMLEndElement:localName prefix:prefix uri:uri]; - } - - [self endStoringCharacters]; - } -} - - -@end - - -static void startElementSAX(void *context, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI, int nb_namespaces, const xmlChar **namespaces, int nb_attributes, int nb_defaulted, const xmlChar **attributes) { - - [(__bridge RSSAXParser *)context xmlStartElement:localname prefix:prefix uri:URI numberOfNamespaces:nb_namespaces namespaces:namespaces numberOfAttributes:nb_attributes numberDefaulted:nb_defaulted attributes:attributes]; -} - - -static void endElementSAX(void *context, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI) { - [(__bridge RSSAXParser *)context xmlEndElement:localname prefix:prefix uri:URI]; -} - - -static void charactersFoundSAX(void *context, const xmlChar *ch, int len) { - [(__bridge RSSAXParser *)context xmlCharactersFound:ch length:(NSUInteger)len]; -} - - -static void endDocumentSAX(void *context) { - [(__bridge RSSAXParser *)context xmlEndDocument]; -} - - -static xmlSAXHandler saxHandlerStruct = { - nil, /* internalSubset */ - nil, /* isStandalone */ - nil, /* hasInternalSubset */ - nil, /* hasExternalSubset */ - nil, /* resolveEntity */ - nil, /* getEntity */ - nil, /* entityDecl */ - nil, /* notationDecl */ - nil, /* attributeDecl */ - nil, /* elementDecl */ - nil, /* unparsedEntityDecl */ - nil, /* setDocumentLocator */ - nil, /* startDocument */ - endDocumentSAX, /* endDocument */ - nil, /* startElement*/ - nil, /* endElement */ - nil, /* reference */ - charactersFoundSAX, /* characters */ - nil, /* ignorableWhitespace */ - nil, /* processingInstruction */ - nil, /* comment */ - nil, /* warning */ - nil, /* error */ - nil, /* fatalError //: unused error() get all the errors */ - nil, /* getParameterEntity */ - nil, /* cdataBlock */ - nil, /* externalSubset */ - XML_SAX2_MAGIC, - nil, - startElementSAX, /* startElementNs */ - endElementSAX, /* endElementNs */ - nil /* serror */ -}; - - -void RSSAXInitLibXMLParser(void) { - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - xmlInitParser(); - }); -} - diff --git a/RSParser/Sources/ObjC/include/RSParser.h b/RSParser/Sources/ObjC/include/RSParser.h deleted file mode 100644 index c9bd0008f..000000000 --- a/RSParser/Sources/ObjC/include/RSParser.h +++ /dev/null @@ -1,56 +0,0 @@ -// -// RSParser.h -// RSParser -// -// Created by Brent Simmons on 6/20/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -@import Foundation; - - -#import "../ParserData.h" -#import "../RSDateParser.h" - -// OPML - -#import "../RSOPMLParser.h" -#import "../RSOPMLDocument.h" -#import "../RSOPMLItem.h" -#import "../RSOPMLAttributes.h" -#import "../RSOPMLFeedSpecifier.h" -#import "../RSOPMLError.h" - -// For writing your own XML parser. - -#import "../RSSAXParser.h" - -// You should use FeedParser (Swift) instead of these two specific parsers -// and the objects they create. -// But they’re available if you want them. - -#import "../RSRSSParser.h" -#import "../RSAtomParser.h" -#import "../RSParsedFeed.h" -#import "../RSParsedArticle.h" -#import "../RSParsedEnclosure.h" -#import "../RSParsedAuthor.h" - -// HTML - -#import "../RSHTMLMetadataParser.h" -#import "../RSHTMLMetadata.h" -#import "../RSHTMLLinkParser.h" -#import "../RSSAXHTMLParser.h" // For writing your own HTML parser. -#import "../RSHTMLTag.h" - -// Utilities - -#import "../NSData+RSParser.h" -#import "../NSString+RSParser.h" - - - - - - diff --git a/RSParser/Sources/Swift/Exports.swift b/RSParser/Sources/Swift/Exports.swift deleted file mode 100644 index c9c30685e..000000000 --- a/RSParser/Sources/Swift/Exports.swift +++ /dev/null @@ -1,9 +0,0 @@ -// -// Exports.swift -// -// -// Created by Stuart Breckenridge on 29/7/20. -// - -import Foundation -@_exported import RSParserObjC diff --git a/RSParser/Sources/Swift/Feeds/FeedParser.swift b/RSParser/Sources/Swift/Feeds/FeedParser.swift deleted file mode 100644 index 96de186b2..000000000 --- a/RSParser/Sources/Swift/Feeds/FeedParser.swift +++ /dev/null @@ -1,91 +0,0 @@ -// -// FeedParser.swift -// RSParser -// -// Created by Brent Simmons on 6/20/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import Foundation -import RSParserObjC - -// FeedParser handles RSS, Atom, JSON Feed, and RSS-in-JSON. -// You don’t need to know the type of feed. - -public typealias FeedParserCallback = (_ parsedFeed: ParsedFeed?, _ error: Error?) -> Void - -public struct FeedParser { - - private static let parseQueue = DispatchQueue(label: "FeedParser parse queue") - - public static func canParse(_ parserData: ParserData) -> Bool { - - let type = feedType(parserData) - - switch type { - case .jsonFeed, .rssInJSON, .rss, .atom: - return true - default: - return false - } - } - - public static func mightBeAbleToParseBasedOnPartialData(_ parserData: ParserData) -> Bool { - - let type = feedType(parserData, isPartialData: true) - - switch type { - case .jsonFeed, .rssInJSON, .rss, .atom, .unknown: - return true - default: - return false - } - } - - public static func parse(_ parserData: ParserData) throws -> ParsedFeed? { - - // This is generally fast enough to call on the main thread — - // but it’s probably a good idea to use a background queue if - // you might be doing a lot of parsing. (Such as in a feed reader.) - - do { - let type = feedType(parserData) - - switch type { - - case .jsonFeed: - return try JSONFeedParser.parse(parserData) - - case .rssInJSON: - return try RSSInJSONParser.parse(parserData) - - case .rss: - return RSSParser.parse(parserData) - - case .atom: - return AtomParser.parse(parserData) - - case .unknown, .notAFeed: - return nil - } - } - catch { throw error } - } - - public static func parse(_ parserData: ParserData, _ completion: @escaping FeedParserCallback) { - - parseQueue.async { - do { - let parsedFeed = try parse(parserData) - DispatchQueue.main.async { - completion(parsedFeed, nil) - } - } - catch { - DispatchQueue.main.async { - completion(nil, error) - } - } - } - } -} diff --git a/RSParser/Sources/Swift/Feeds/FeedParserError.swift b/RSParser/Sources/Swift/Feeds/FeedParserError.swift deleted file mode 100644 index 83ccbd059..000000000 --- a/RSParser/Sources/Swift/Feeds/FeedParserError.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// FeedParserError.swift -// RSParser -// -// Created by Brent Simmons on 6/24/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -public struct FeedParserError: Error { - - public enum FeedParserErrorType { - - case rssChannelNotFound - case rssItemsNotFound - case jsonFeedVersionNotFound - case jsonFeedItemsNotFound - case jsonFeedTitleNotFound - case invalidJSON - } - - public let errorType: FeedParserErrorType - - public init(_ errorType: FeedParserErrorType) { - - self.errorType = errorType - } -} diff --git a/RSParser/Sources/Swift/Feeds/FeedType.swift b/RSParser/Sources/Swift/Feeds/FeedType.swift deleted file mode 100644 index bfe33c0c7..000000000 --- a/RSParser/Sources/Swift/Feeds/FeedType.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// FeedType.swift -// RSParser -// -// Created by Brent Simmons on 6/20/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import Foundation -#if SWIFT_PACKAGE -import RSParserObjC -#endif - -public enum FeedType { - case rss - case atom - case jsonFeed - case rssInJSON - case unknown - case notAFeed -} - - -private let minNumberOfBytesRequired = 128 - -public func feedType(_ parserData: ParserData, isPartialData: Bool = false) -> FeedType { - - // Can call with partial data — while still downloading, for instance. - // If there’s not enough data, return .unknown. Ask again when there’s more data. - // If it’s definitely not a feed, return .notAFeed. - // - // This is fast enough to call on the main thread. - - if parserData.data.count < minNumberOfBytesRequired { - return .unknown - } - - let nsdata = parserData.data as NSData - - if nsdata.isProbablyJSONFeed() { - return .jsonFeed - } - if nsdata.isProbablyRSSInJSON() { - return .rssInJSON - } - if nsdata.isProbablyRSS() { - return .rss - } - if nsdata.isProbablyAtom() { - return .atom - } - - if isPartialData && nsdata.isProbablyJSON() { - // Might not be able to detect a JSON Feed without all data. - // Dr. Drang’s JSON Feed (see althis.json and allthis-partial.json in tests) - // has, at this writing, the JSON version element at the end of the feed, - // which is totally legal — but it means not being able to detect - // that it’s a JSON Feed without all the data. - // So this returns .unknown instead of .notAFeed. - return .unknown - } - - return .notAFeed -} diff --git a/RSParser/Sources/Swift/Feeds/JSON/JSONFeedParser.swift b/RSParser/Sources/Swift/Feeds/JSON/JSONFeedParser.swift deleted file mode 100644 index ed528419c..000000000 --- a/RSParser/Sources/Swift/Feeds/JSON/JSONFeedParser.swift +++ /dev/null @@ -1,250 +0,0 @@ -// -// JSONFeedParser.swift -// RSParser -// -// Created by Brent Simmons on 6/25/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import Foundation -#if SWIFT_PACKAGE -import RSParserObjC -#endif - -// See https://jsonfeed.org/version/1.1 - -public struct JSONFeedParser { - - struct Key { - static let version = "version" - static let items = "items" - static let title = "title" - static let homePageURL = "home_page_url" - static let feedURL = "feed_url" - static let feedDescription = "description" - static let nextURL = "next_url" - static let icon = "icon" - static let favicon = "favicon" - static let expired = "expired" - static let author = "author" - static let authors = "authors" - static let name = "name" - static let url = "url" - static let avatar = "avatar" - static let hubs = "hubs" - static let type = "type" - static let contentHTML = "content_html" - static let contentText = "content_text" - static let externalURL = "external_url" - static let summary = "summary" - static let image = "image" - static let bannerImage = "banner_image" - static let datePublished = "date_published" - static let dateModified = "date_modified" - static let tags = "tags" - static let uniqueID = "id" - static let attachments = "attachments" - static let mimeType = "mime_type" - static let sizeInBytes = "size_in_bytes" - static let durationInSeconds = "duration_in_seconds" - static let language = "language" - } - - static let jsonFeedVersionMarker = "://jsonfeed.org/version/" // Allow for the mistake of not getting the scheme exactly correct. - - public static func parse(_ parserData: ParserData) throws -> ParsedFeed? { - - guard let d = JSONUtilities.dictionary(with: parserData.data) else { - throw FeedParserError(.invalidJSON) - } - - guard let version = d[Key.version] as? String, let _ = version.range(of: JSONFeedParser.jsonFeedVersionMarker) else { - throw FeedParserError(.jsonFeedVersionNotFound) - } - guard let itemsArray = d[Key.items] as? JSONArray else { - throw FeedParserError(.jsonFeedItemsNotFound) - } - guard let title = d[Key.title] as? String else { - throw FeedParserError(.jsonFeedTitleNotFound) - } - - let authors = parseAuthors(d) - let homePageURL = d[Key.homePageURL] as? String - let feedURL = d[Key.feedURL] as? String ?? parserData.url - let feedDescription = d[Key.feedDescription] as? String - let nextURL = d[Key.nextURL] as? String - let iconURL = d[Key.icon] as? String - let faviconURL = d[Key.favicon] as? String - let expired = d[Key.expired] as? Bool ?? false - let hubs = parseHubs(d) - let language = d[Key.language] as? String - - let items = parseItems(itemsArray, parserData.url) - - return ParsedFeed(type: .jsonFeed, title: title, homePageURL: homePageURL, feedURL: feedURL, language: language, feedDescription: feedDescription, nextURL: nextURL, iconURL: iconURL, faviconURL: faviconURL, authors: authors, expired: expired, hubs: hubs, items: items) - } -} - -private extension JSONFeedParser { - - static func parseAuthors(_ dictionary: JSONDictionary) -> Set? { - - if let authorsArray = dictionary[Key.authors] as? JSONArray { - var authors = Set() - for author in authorsArray { - if let parsedAuthor = parseAuthor(author) { - authors.insert(parsedAuthor) - } - } - return authors - } - - guard let authorDictionary = dictionary[Key.author] as? JSONDictionary, - let parsedAuthor = parseAuthor(authorDictionary) else { - return nil - } - - return Set([parsedAuthor]) - } - - static func parseAuthor(_ dictionary: JSONDictionary) -> ParsedAuthor? { - let name = dictionary[Key.name] as? String - let url = dictionary[Key.url] as? String - let avatar = dictionary[Key.avatar] as? String - if name == nil && url == nil && avatar == nil { - return nil - } - return ParsedAuthor(name: name, url: url, avatarURL: avatar, emailAddress: nil) - } - - static func parseHubs(_ dictionary: JSONDictionary) -> Set? { - - guard let hubsArray = dictionary[Key.hubs] as? JSONArray else { - return nil - } - - let hubs = hubsArray.compactMap { (hubDictionary) -> ParsedHub? in - guard let hubURL = hubDictionary[Key.url] as? String, let hubType = hubDictionary[Key.type] as? String else { - return nil - } - return ParsedHub(type: hubType, url: hubURL) - } - return hubs.isEmpty ? nil : Set(hubs) - } - - static func parseItems(_ itemsArray: JSONArray, _ feedURL: String) -> Set { - - return Set(itemsArray.compactMap { (oneItemDictionary) -> ParsedItem? in - return parseItem(oneItemDictionary, feedURL) - }) - } - - static func parseItem(_ itemDictionary: JSONDictionary, _ feedURL: String) -> ParsedItem? { - - guard let uniqueID = parseUniqueID(itemDictionary) else { - return nil - } - - let contentHTML = itemDictionary[Key.contentHTML] as? String - let contentText = itemDictionary[Key.contentText] as? String - if contentHTML == nil && contentText == nil { - return nil - } - - let url = itemDictionary[Key.url] as? String - let externalURL = itemDictionary[Key.externalURL] as? String - let title = parseTitle(itemDictionary, feedURL) - let language = itemDictionary[Key.language] as? String - let summary = itemDictionary[Key.summary] as? String - let imageURL = itemDictionary[Key.image] as? String - let bannerImageURL = itemDictionary[Key.bannerImage] as? String - - let datePublished = parseDate(itemDictionary[Key.datePublished] as? String) - let dateModified = parseDate(itemDictionary[Key.dateModified] as? String) - - let authors = parseAuthors(itemDictionary) - var tags: Set? = nil - if let tagsArray = itemDictionary[Key.tags] as? [String] { - tags = Set(tagsArray) - } - let attachments = parseAttachments(itemDictionary) - - return ParsedItem(syncServiceID: nil, uniqueID: uniqueID, feedURL: feedURL, url: url, externalURL: externalURL, title: title, language: language, contentHTML: contentHTML, contentText: contentText, summary: summary, imageURL: imageURL, bannerImageURL: bannerImageURL, datePublished: datePublished, dateModified: dateModified, authors: authors, tags: tags, attachments: attachments) - } - - static func parseTitle(_ itemDictionary: JSONDictionary, _ feedURL: String) -> String? { - - guard let title = itemDictionary[Key.title] as? String else { - return nil - } - - if isSpecialCaseTitleWithEntitiesFeed(feedURL) { - return (title as NSString).rsparser_stringByDecodingHTMLEntities() - } - - return title - } - - static func isSpecialCaseTitleWithEntitiesFeed(_ feedURL: String) -> Bool { - - // As of 16 Feb. 2018, Kottke’s and Heer’s feeds includes HTML entities in the title elements. - // If we find more feeds like this, we’ll add them here. If these feeds get fixed, we’ll remove them. - - let lowerFeedURL = feedURL.lowercased() - let matchStrings = ["kottke.org", "pxlnv.com", "macstories.net", "macobserver.com"] - for matchString in matchStrings { - if lowerFeedURL.contains(matchString) { - return true - } - } - - return false - } - - static func parseUniqueID(_ itemDictionary: JSONDictionary) -> String? { - - if let uniqueID = itemDictionary[Key.uniqueID] as? String { - return uniqueID // Spec says it must be a string - } - // Version 1 spec also says that if it’s a number, even though that’s incorrect, it should be coerced to a string. - if let uniqueID = itemDictionary[Key.uniqueID] as? Int { - return "\(uniqueID)" - } - if let uniqueID = itemDictionary[Key.uniqueID] as? Double { - return "\(uniqueID)" - } - return nil - } - - static func parseDate(_ dateString: String?) -> Date? { - - guard let dateString = dateString, !dateString.isEmpty else { - return nil - } - return RSDateWithString(dateString) - } - - static func parseAttachments(_ itemDictionary: JSONDictionary) -> Set? { - - guard let attachmentsArray = itemDictionary[Key.attachments] as? JSONArray else { - return nil - } - return Set(attachmentsArray.compactMap { parseAttachment($0) }) - } - - static func parseAttachment(_ attachmentObject: JSONDictionary) -> ParsedAttachment? { - - guard let url = attachmentObject[Key.url] as? String else { - return nil - } - guard let mimeType = attachmentObject[Key.mimeType] as? String else { - return nil - } - - let title = attachmentObject[Key.title] as? String - let sizeInBytes = attachmentObject[Key.sizeInBytes] as? Int - let durationInSeconds = attachmentObject[Key.durationInSeconds] as? Int - - return ParsedAttachment(url: url, mimeType: mimeType, title: title, sizeInBytes: sizeInBytes, durationInSeconds: durationInSeconds) - } -} diff --git a/RSParser/Sources/Swift/Feeds/JSON/RSSInJSONParser.swift b/RSParser/Sources/Swift/Feeds/JSON/RSSInJSONParser.swift deleted file mode 100644 index ea1907378..000000000 --- a/RSParser/Sources/Swift/Feeds/JSON/RSSInJSONParser.swift +++ /dev/null @@ -1,184 +0,0 @@ -// -// RSSInJSONParser.swift -// RSParser -// -// Created by Brent Simmons on 6/24/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import Foundation -#if SWIFT_PACKAGE -import RSParserObjC -#endif - -// See https://github.com/scripting/Scripting-News/blob/master/rss-in-json/README.md -// Also: http://cyber.harvard.edu/rss/rss.html - -public struct RSSInJSONParser { - - public static func parse(_ parserData: ParserData) throws -> ParsedFeed? { - - do { - guard let parsedObject = try JSONSerialization.jsonObject(with: parserData.data) as? JSONDictionary else { - throw FeedParserError(.invalidJSON) - } - guard let rssObject = parsedObject["rss"] as? JSONDictionary else { - throw FeedParserError(.rssChannelNotFound) - } - guard let channelObject = rssObject["channel"] as? JSONDictionary else { - throw FeedParserError(.rssChannelNotFound) - } - - // I’d bet money that in practice the items array won’t always appear correctly inside the channel object. - // I’d also bet that sometimes it gets called "items" instead of "item". - var itemsObject = channelObject["item"] as? JSONArray - if itemsObject == nil { - itemsObject = parsedObject["item"] as? JSONArray - } - if itemsObject == nil { - itemsObject = channelObject["items"] as? JSONArray - } - if itemsObject == nil { - itemsObject = parsedObject["items"] as? JSONArray - } - if itemsObject == nil { - throw FeedParserError(.rssItemsNotFound) - } - - let title = channelObject["title"] as? String - let homePageURL = channelObject["link"] as? String - let feedURL = parserData.url - let feedDescription = channelObject["description"] as? String - let feedLanguage = channelObject["language"] as? String - - let items = parseItems(itemsObject!, parserData.url) - - return ParsedFeed(type: .rssInJSON, title: title, homePageURL: homePageURL, feedURL: feedURL, language: feedLanguage, feedDescription: feedDescription, nextURL: nil, iconURL: nil, faviconURL: nil, authors: nil, expired: false, hubs: nil, items: items) - - } - catch { throw error } - } -} - -private extension RSSInJSONParser { - - static func parseItems(_ itemsObject: JSONArray, _ feedURL: String) -> Set { - - return Set(itemsObject.compactMap{ (oneItemDictionary) -> ParsedItem? in - - return parsedItemWithDictionary(oneItemDictionary, feedURL) - }) - } - - static func parsedItemWithDictionary(_ itemDictionary: JSONDictionary, _ feedURL: String) -> ParsedItem? { - - let externalURL = itemDictionary["link"] as? String - let title = itemDictionary["title"] as? String - - var contentHTML = itemDictionary["description"] as? String - var contentText: String? = nil - if contentHTML != nil && !(contentHTML!.contains("<")) { - contentText = contentHTML - contentHTML = nil - } - if contentHTML == nil && contentText == nil && title == nil { - return nil - } - - var datePublished: Date? = nil - if let datePublishedString = itemDictionary["pubDate"] as? String { - datePublished = RSDateWithString(datePublishedString) - } - - let authors = parseAuthors(itemDictionary) - let tags = parseTags(itemDictionary) - let attachments = parseAttachments(itemDictionary) - - var uniqueID: String? = itemDictionary["guid"] as? String - if uniqueID == nil { - - // Calculate a uniqueID based on a combination of non-empty elements. Then hash the result. - // Items should have guids. When they don't, re-runs are very likely - // because there's no other 100% reliable way to determine identity. - // This calculated uniqueID is valid only for this particular feed. (Just like ids in JSON Feed.) - - var s = "" - if let datePublished = datePublished { - s += "\(datePublished.timeIntervalSince1970)" - } - if let title = title { - s += title - } - if let externalURL = externalURL { - s += externalURL - } - if let authorEmailAddress = authors?.first?.emailAddress { - s += authorEmailAddress - } - if let oneAttachmentURL = attachments?.first?.url { - s += oneAttachmentURL - } - if s.isEmpty { - // Sheesh. Tough case. - if let _ = contentHTML { - s = contentHTML! - } - if let _ = contentText { - s = contentText! - } - } - uniqueID = (s as NSString).rsparser_md5Hash() - } - - if let uniqueID = uniqueID { - return ParsedItem(syncServiceID: nil, uniqueID: uniqueID, feedURL: feedURL, url: nil, externalURL: externalURL, title: title, language: nil, contentHTML: contentHTML, contentText: contentText, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: datePublished, dateModified: nil, authors: authors, tags: tags, attachments: attachments) - } - return nil - } - - static func parseAuthors(_ itemDictionary: JSONDictionary) -> Set? { - - guard let authorEmailAddress = itemDictionary["author"] as? String else { - return nil - } - let parsedAuthor = ParsedAuthor(name: nil, url: nil, avatarURL: nil, emailAddress: authorEmailAddress) - return Set([parsedAuthor]) - } - - static func parseTags(_ itemDictionary: JSONDictionary) -> Set? { - - if let categoryObject = itemDictionary["category"] as? JSONDictionary { - if let oneTag = categoryObject["#value"] as? String { - return Set([oneTag]) - } - return nil - } - else if let categoryArray = itemDictionary["category"] as? JSONArray { - return Set(categoryArray.compactMap{ $0["#value"] as? String }) - } - return nil - } - - static func parseAttachments(_ itemDictionary: JSONDictionary) -> Set? { - - guard let enclosureObject = itemDictionary["enclosure"] as? JSONDictionary else { - return nil - } - guard let attachmentURL = enclosureObject["url"] as? String else { - return nil - } - - var attachmentSize = enclosureObject["length"] as? Int - if attachmentSize == nil { - if let attachmentSizeString = enclosureObject["length"] as? String { - attachmentSize = (attachmentSizeString as NSString).integerValue - } - } - - let type = enclosureObject["type"] as? String - if let attachment = ParsedAttachment(url: attachmentURL, mimeType: type, title: nil, sizeInBytes: attachmentSize, durationInSeconds: nil) { - return Set([attachment]) - } - return nil - } -} diff --git a/RSParser/Sources/Swift/Feeds/ParsedAttachment.swift b/RSParser/Sources/Swift/Feeds/ParsedAttachment.swift deleted file mode 100644 index 529f773cb..000000000 --- a/RSParser/Sources/Swift/Feeds/ParsedAttachment.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// ParsedAttachment.swift -// RSParser -// -// Created by Brent Simmons on 6/20/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -public struct ParsedAttachment: Hashable { - - public let url: String - public let mimeType: String? - public let title: String? - public let sizeInBytes: Int? - public let durationInSeconds: Int? - - public 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 - self.sizeInBytes = sizeInBytes - self.durationInSeconds = durationInSeconds - } - - // MARK: - Hashable - - public func hash(into hasher: inout Hasher) { - hasher.combine(url) - } -} diff --git a/RSParser/Sources/Swift/Feeds/ParsedAuthor.swift b/RSParser/Sources/Swift/Feeds/ParsedAuthor.swift deleted file mode 100644 index f7b9e9136..000000000 --- a/RSParser/Sources/Swift/Feeds/ParsedAuthor.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// ParsedAuthor.swift -// RSParser -// -// Created by Brent Simmons on 6/20/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -public struct ParsedAuthor: Hashable, Codable { - - public let name: String? - public let url: String? - public let avatarURL: String? - public let emailAddress: String? - - public init(name: String?, url: String?, avatarURL: String?, emailAddress: String?) { - self.name = name - self.url = url - self.avatarURL = avatarURL - self.emailAddress = emailAddress - } - - // MARK: - Hashable - - public func hash(into hasher: inout Hasher) { - if let name = name { - hasher.combine(name) - } - else if let url = url { - hasher.combine(url) - } - else if let emailAddress = emailAddress { - hasher.combine(emailAddress) - } - else if let avatarURL = avatarURL { - hasher.combine(avatarURL) - } - else { - hasher.combine("") - } - } -} diff --git a/RSParser/Sources/Swift/Feeds/ParsedFeed.swift b/RSParser/Sources/Swift/Feeds/ParsedFeed.swift deleted file mode 100644 index 332346a15..000000000 --- a/RSParser/Sources/Swift/Feeds/ParsedFeed.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// ParsedFeed.swift -// RSParser -// -// Created by Brent Simmons on 6/20/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -public struct ParsedFeed { - - public let type: FeedType - public let title: String? - public let homePageURL: String? - public let feedURL: String? - public let language: String? - public let feedDescription: String? - public let nextURL: String? - public let iconURL: String? - public let faviconURL: String? - public let authors: Set? - public let expired: Bool - public let hubs: Set? - public let items: Set - - public init(type: FeedType, title: String?, homePageURL: String?, feedURL: String?, language: String?, feedDescription: String?, nextURL: String?, iconURL: String?, faviconURL: String?, authors: Set?, expired: Bool, hubs: Set?, items: Set) { - self.type = type - self.title = title - self.homePageURL = homePageURL?.nilIfEmptyOrWhitespace - self.feedURL = feedURL - self.language = language - self.feedDescription = feedDescription - self.nextURL = nextURL - self.iconURL = iconURL - self.faviconURL = faviconURL - self.authors = authors - self.expired = expired - self.hubs = hubs - self.items = items - } -} diff --git a/RSParser/Sources/Swift/Feeds/ParsedHub.swift b/RSParser/Sources/Swift/Feeds/ParsedHub.swift deleted file mode 100644 index 1f7d52e81..000000000 --- a/RSParser/Sources/Swift/Feeds/ParsedHub.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// ParsedHub.swift -// RSParser -// -// Created by Brent Simmons on 6/20/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -public struct ParsedHub: Hashable { - - public let type: String - public let url: String -} diff --git a/RSParser/Sources/Swift/Feeds/ParsedItem.swift b/RSParser/Sources/Swift/Feeds/ParsedItem.swift deleted file mode 100644 index 4e019c480..000000000 --- a/RSParser/Sources/Swift/Feeds/ParsedItem.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// ParsedItem.swift -// RSParser -// -// Created by Brent Simmons on 6/20/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -public struct ParsedItem: Hashable { - - public let syncServiceID: String? //Nil when not syncing - public let uniqueID: String //RSS guid, for instance; may be calculated - public let feedURL: String - public let url: String? - public let externalURL: String? - public let title: String? - public let language: String? - public let contentHTML: String? - public let contentText: String? - public let summary: String? - public let imageURL: String? - public let bannerImageURL: String? - public let datePublished: Date? - public let dateModified: Date? - public let authors: Set? - public let tags: Set? - public let attachments: Set? - - public init(syncServiceID: String?, uniqueID: String, feedURL: String, url: String?, externalURL: String?, title: String?, - language: String?, contentHTML: String?, contentText: String?, summary: String?, imageURL: String?, - bannerImageURL: String?,datePublished: Date?, dateModified: Date?, authors: Set?, - tags: Set?, attachments: Set?) { - - self.syncServiceID = syncServiceID - self.uniqueID = uniqueID - self.feedURL = feedURL - self.url = url - self.externalURL = externalURL - self.title = title - self.language = language - self.contentHTML = contentHTML - self.contentText = contentText - self.summary = summary - self.imageURL = imageURL - self.bannerImageURL = bannerImageURL - self.datePublished = datePublished - self.dateModified = dateModified - self.authors = authors - self.tags = tags - self.attachments = attachments - } - - // MARK: - Hashable - - public func hash(into hasher: inout Hasher) { - if let syncServiceID = syncServiceID { - hasher.combine(syncServiceID) - } - else { - hasher.combine(uniqueID) - hasher.combine(feedURL) - } - } -} - diff --git a/RSParser/Sources/Swift/Feeds/XML/AtomParser.swift b/RSParser/Sources/Swift/Feeds/XML/AtomParser.swift deleted file mode 100644 index ad6b6648f..000000000 --- a/RSParser/Sources/Swift/Feeds/XML/AtomParser.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// AtomParser.swift -// RSParser -// -// Created by Brent Simmons on 6/25/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -#if SWIFT_PACKAGE -import RSParserObjC -#endif - -// RSSParser wraps the Objective-C RSAtomParser. -// -// The Objective-C parser creates RSParsedFeed, RSParsedArticle, etc. -// This wrapper then creates ParsedFeed, ParsedItem, etc. so that it creates -// the same things that JSONFeedParser and RSSInJSONParser create. -// -// In general, you should see FeedParser.swift for all your feed-parsing needs. - -public struct AtomParser { - - public static func parse(_ parserData: ParserData) -> ParsedFeed? { - - if let rsParsedFeed = RSAtomParser.parseFeed(with: parserData) { - return RSParsedFeedTransformer.parsedFeed(rsParsedFeed) - } - return nil - } -} diff --git a/RSParser/Sources/Swift/Feeds/XML/RSParsedFeedTransformer.swift b/RSParser/Sources/Swift/Feeds/XML/RSParsedFeedTransformer.swift deleted file mode 100644 index 4d680acff..000000000 --- a/RSParser/Sources/Swift/Feeds/XML/RSParsedFeedTransformer.swift +++ /dev/null @@ -1,80 +0,0 @@ -// -// RSParsedFeedTransformer.swift -// RSParser -// -// Created by Brent Simmons on 6/25/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import Foundation -#if SWIFT_PACKAGE -import RSParserObjC -#endif - -// RSRSSParser and RSAtomParser were written in Objective-C quite a while ago. -// They create an RSParsedFeed object and related Objective-C objects. -// These functions take an RSParsedFeed and return a Swift-y ParsedFeed, -// which is part of providing a single API for feed parsing. - -struct RSParsedFeedTransformer { - - static func parsedFeed(_ rsParsedFeed: RSParsedFeed) -> ParsedFeed { - - let items = parsedItems(rsParsedFeed.articles) - return ParsedFeed(type: .rss, title: rsParsedFeed.title, homePageURL: rsParsedFeed.link, feedURL: rsParsedFeed.urlString, language: rsParsedFeed.language, feedDescription: nil, nextURL: nil, iconURL: nil, faviconURL: nil, authors: nil, expired: false, hubs: nil, items: items) - } -} - -private extension RSParsedFeedTransformer { - - static func parsedItems(_ parsedArticles: Set) -> Set { - - // Create Set from Set - - return Set(parsedArticles.map(parsedItem)) - } - - static func parsedItem(_ parsedArticle: RSParsedArticle) -> ParsedItem { - - let uniqueID = parsedArticle.articleID - let url = parsedArticle.permalink - let externalURL = parsedArticle.link - let title = parsedArticle.title - let language = parsedArticle.language - let contentHTML = parsedArticle.body - let datePublished = parsedArticle.datePublished - let dateModified = parsedArticle.dateModified - let authors = parsedAuthors(parsedArticle.authors) - let attachments = parsedAttachments(parsedArticle.enclosures) - - return ParsedItem(syncServiceID: nil, uniqueID: uniqueID, feedURL: parsedArticle.feedURL, url: url, externalURL: externalURL, title: title, language: language, contentHTML: contentHTML, contentText: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: datePublished, dateModified: dateModified, authors: authors, tags: nil, attachments: attachments) - } - - static func parsedAuthors(_ authors: Set?) -> Set? { - - guard let authors = authors, !authors.isEmpty else { - return nil - } - - let transformedAuthors = authors.compactMap { (author) -> ParsedAuthor? in - return ParsedAuthor(name: author.name, url: author.url, avatarURL: nil, emailAddress: author.emailAddress) - } - - return transformedAuthors.isEmpty ? nil : Set(transformedAuthors) - } - - static func parsedAttachments(_ enclosures: Set?) -> Set? { - - guard let enclosures = enclosures, !enclosures.isEmpty else { - return nil - } - - let attachments = enclosures.compactMap { (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/RSParser/Sources/Swift/Feeds/XML/RSSParser.swift b/RSParser/Sources/Swift/Feeds/XML/RSSParser.swift deleted file mode 100644 index f14d9c75d..000000000 --- a/RSParser/Sources/Swift/Feeds/XML/RSSParser.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// RSSParser.swift -// RSParser -// -// Created by Brent Simmons on 6/25/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import Foundation -import RSParserObjC - -// RSSParser wraps the Objective-C RSRSSParser. -// -// The Objective-C parser creates RSParsedFeed, RSParsedArticle, etc. -// This wrapper then creates ParsedFeed, ParsedItem, etc. so that it creates -// the same things that JSONFeedParser and RSSInJSONParser create. -// -// In general, you should see FeedParser.swift for all your feed-parsing needs. - -public struct RSSParser { - - public static func parse(_ parserData: ParserData) -> ParsedFeed? { - - if let rsParsedFeed = RSRSSParser.parseFeed(with: parserData) { - return RSParsedFeedTransformer.parsedFeed(rsParsedFeed) - } - return nil - } -} diff --git a/RSParser/Sources/Swift/JSON/JSONTypes.swift b/RSParser/Sources/Swift/JSON/JSONTypes.swift deleted file mode 100644 index fa3039a31..000000000 --- a/RSParser/Sources/Swift/JSON/JSONTypes.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// JSONDictionary.swift -// RSParser -// -// Created by Brent Simmons on 6/24/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -public typealias JSONDictionary = [String: Any] -public typealias JSONArray = [JSONDictionary] diff --git a/RSParser/Sources/Swift/JSON/JSONUtilities.swift b/RSParser/Sources/Swift/JSON/JSONUtilities.swift deleted file mode 100644 index 2319e0389..000000000 --- a/RSParser/Sources/Swift/JSON/JSONUtilities.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// JSONUtilities.swift -// RSParser -// -// Created by Brent Simmons on 12/10/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -public struct JSONUtilities { - - public static func object(with data: Data) -> Any? { - - return try? JSONSerialization.jsonObject(with: data) - } - - public static func dictionary(with data: Data) -> JSONDictionary? { - - return object(with: data) as? JSONDictionary - } - - public static func array(with data: Data) -> JSONArray? { - - return object(with: data) as? JSONArray - } -} diff --git a/RSParser/Sources/Swift/Utilities/String+RSParser.swift b/RSParser/Sources/Swift/Utilities/String+RSParser.swift deleted file mode 100644 index 9922be93a..000000000 --- a/RSParser/Sources/Swift/Utilities/String+RSParser.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// String+RSParser.swift -// RSParser -// -// Created by Nate Weaver on 2020-01-19. -// Copyright © 2020 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -extension String { - - var nilIfEmptyOrWhitespace: String? { - return self.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? nil : self - } - -} diff --git a/RSParser/Tests/RSParserTests/AtomParserTests.swift b/RSParser/Tests/RSParserTests/AtomParserTests.swift deleted file mode 100644 index 154c17e9d..000000000 --- a/RSParser/Tests/RSParserTests/AtomParserTests.swift +++ /dev/null @@ -1,107 +0,0 @@ -// -// AtomParserTests.swift -// RSParser -// -// Created by Brent Simmons on 6/26/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import XCTest -import RSParser - -class AtomParserTests: XCTestCase { - - func testDaringFireballPerformance() { - - // 0.009 sec on my 2012 iMac. - let d = parserData("DaringFireball", "atom", "http://daringfireball.net/") //It’s actually an Atom feed - self.measure { - let _ = try! FeedParser.parse(d) - } - } - - func testAllThisPerformance() { - - // 0.003 sec on my 2012 iMac. - let d = parserData("allthis", "atom", "http://leancrew.com/all-this") - self.measure { - let _ = try! FeedParser.parse(d) - } - } - - func testGettingHomePageLink() { - - let d = parserData("allthis", "atom", "http://leancrew.com/all-this") - let parsedFeed = try! FeedParser.parse(d)! - - XCTAssertTrue(parsedFeed.homePageURL == "http://leancrew.com/all-this") - } - - func testDaringFireball() { - - let d = parserData("DaringFireball", "atom", "http://daringfireball.net/") //It’s actually an Atom feed - let parsedFeed = try! FeedParser.parse(d)! - - for article in parsedFeed.items { - - XCTAssertNotNil(article.url) - - XCTAssertTrue(article.uniqueID.hasPrefix("tag:daringfireball.net,2017:/")) - - XCTAssertEqual(article.authors!.count, 1) // TODO: parse Atom authors - let author = article.authors!.first! - if author.name == "Daring Fireball Department of Commerce" { - XCTAssertNil(author.url) - } - else { - XCTAssertEqual(author.name, "John Gruber") - XCTAssertEqual(author.url, "http://daringfireball.net/") - } - - XCTAssertNotNil(article.datePublished) - XCTAssert(article.attachments == nil) - - XCTAssertEqual(article.language, "en") - } - } - - func test4fsodonlineAttachments() { - - // Thanks to Marco for finding me some Atom podcast feeds. Apparently they’re super-rare. - - let d = parserData("4fsodonline", "atom", "http://4fsodonline.blogspot.com/") - let parsedFeed = try! FeedParser.parse(d)! - - for article in parsedFeed.items { - - XCTAssertTrue(article.attachments!.count > 0) - let attachment = article.attachments!.first! - - XCTAssertTrue(attachment.url.hasPrefix("http://www.blogger.com/video-play.mp4?")) - XCTAssertNil(attachment.sizeInBytes) - XCTAssertEqual(attachment.mimeType!, "video/mp4") - } - } - - func testExpertOpinionENTAttachments() { - - // Another from Marco. - - let d = parserData("expertopinionent", "atom", "http://expertopinionent.typepad.com/my-blog/") - let parsedFeed = try! FeedParser.parse(d)! - - for article in parsedFeed.items { - - guard let attachments = article.attachments else { - continue - } - - XCTAssertEqual(attachments.count, 1) - let attachment = attachments.first! - - XCTAssertTrue(attachment.url.hasSuffix(".mp3")) - XCTAssertNil(attachment.sizeInBytes) - XCTAssertEqual(attachment.mimeType!, "audio/mpeg") - } - } -} diff --git a/RSParser/Tests/RSParserTests/EntityDecodingTests.swift b/RSParser/Tests/RSParserTests/EntityDecodingTests.swift deleted file mode 100644 index 31a82846f..000000000 --- a/RSParser/Tests/RSParserTests/EntityDecodingTests.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// EntityDecodingTests.swift -// RSParserTests -// -// Created by Brent Simmons on 12/30/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import XCTest -import RSParser - -class EntityDecodingTests: XCTestCase { - - func test39Decoding() { - - // Bug found by Manton Reece — the ' entity was not getting decoded by NetNewsWire in JSON Feeds from micro.blog. - - let s = "These are the times that try men's souls." - let decoded = s.rsparser_stringByDecodingHTMLEntities() - - XCTAssertEqual(decoded, "These are the times that try men's souls.") - } - - func testEntities() { - var s = "…" - var decoded = s.rsparser_stringByDecodingHTMLEntities() - - XCTAssertEqual(decoded, "…") - - s = "…" - decoded = s.rsparser_stringByDecodingHTMLEntities() - XCTAssertEqual(decoded, "…") - - s = "'" - decoded = s.rsparser_stringByDecodingHTMLEntities() - XCTAssertEqual(decoded, "'") - - s = "§" - decoded = s.rsparser_stringByDecodingHTMLEntities() - XCTAssertEqual(decoded, "§") - - s = "£" - decoded = s.rsparser_stringByDecodingHTMLEntities() - XCTAssertEqual(decoded, "£") - - } -} diff --git a/RSParser/Tests/RSParserTests/FeedParserTypeTests.swift b/RSParser/Tests/RSParserTests/FeedParserTypeTests.swift deleted file mode 100644 index 5cb631fc8..000000000 --- a/RSParser/Tests/RSParserTests/FeedParserTypeTests.swift +++ /dev/null @@ -1,245 +0,0 @@ -// -// FeedParserTypeTests.swift -// RSParser -// -// Created by Brent Simmons on 6/25/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import XCTest -import RSParser -import RSParserObjC - -class FeedParserTypeTests: XCTestCase { - - // MARK: HTML - - func testDaringFireballHTMLType() { - - let d = parserData("DaringFireball", "html", "http://daringfireball.net/") - let type = feedType(d) - XCTAssertTrue(type == .notAFeed) - } - - func testFurboHTMLType() { - - let d = parserData("furbo", "html", "http://furbo.org/") - let type = feedType(d) - XCTAssertTrue(type == .notAFeed) - } - - func testInessentialHTMLType() { - - let d = parserData("inessential", "html", "http://inessential.com/") - let type = feedType(d) - XCTAssertTrue(type == .notAFeed) - } - - func testSixColorsHTMLType() { - - let d = parserData("sixcolors", "html", "https://sixcolors.com/") - let type = feedType(d) - XCTAssertTrue(type == .notAFeed) - } - - // MARK: RSS - - func testEMarleyRSSType() { - - let d = parserData("EMarley", "rss", "https://medium.com/@emarley") - let type = feedType(d) - XCTAssertTrue(type == .rss) - } - - func testScriptingNewsRSSType() { - - let d = parserData("scriptingNews", "rss", "http://scripting.com/") - let type = feedType(d) - XCTAssertTrue(type == .rss) - } - - func testKatieFloydRSSType() { - - let d = parserData("KatieFloyd", "rss", "https://katiefloyd.com/") - let type = feedType(d) - XCTAssertTrue(type == .rss) - } - - func testMantonRSSType() { - - let d = parserData("manton", "rss", "http://manton.org/") - let type = feedType(d) - XCTAssertTrue(type == .rss) - } - - func testDCRainmakerRSSType() { - - let d = parserData("dcrainmaker", "xml", "https://www.dcrainmaker.com/") - let type = feedType(d) - XCTAssertTrue(type == .rss) - } - - func testMacworldRSSType() { - - let d = parserData("macworld", "rss", "https://www.macworld.com/") - let type = feedType(d) - XCTAssertTrue(type == .rss) - } - - func testNatashaTheRobotRSSType() { - - let d = parserData("natasha", "xml", "https://www.natashatherobot.com/") - let type = feedType(d) - XCTAssertTrue(type == .rss) - } - - func testDontHitSaveRSSWithBOMType() { - - let d = parserData("donthitsave", "xml", "http://donthitsave.com/donthitsavefeed.xml") - let type = feedType(d) - XCTAssertTrue(type == .rss) - } - - func testBioRDF() { - let d = parserData("bio", "rdf", "http://connect.biorxiv.org/") - let type = feedType(d) - XCTAssertTrue(type == .rss) - } - - func testPHPXML() { - let d = parserData("phpxml", "rss", "https://www.fcutrecht.net/") - let type = feedType(d) - XCTAssertTrue(type == .rss) - } - - // MARK: Atom - - func testDaringFireballAtomType() { - - // File extension is .rss, but it’s really an Atom feed. - let d = parserData("DaringFireball", "rss", "http://daringfireball.net/") - let type = feedType(d) - XCTAssertTrue(type == .atom) - } - - func testOneFootTsunamiAtomType() { - - let d = parserData("OneFootTsunami", "atom", "http://onefoottsunami.com/") - let type = feedType(d) - XCTAssertTrue(type == .atom) - } - - func testRussCoxAtomType() { - let d = parserData("russcox", "atom", "https://research.swtch.com/") - let type = feedType(d) - XCTAssertTrue(type == .atom) - } - - // MARK: RSS-in-JSON - - func testScriptingNewsJSONType() { - - let d = parserData("ScriptingNews", "json", "http://scripting.com/") - let type = feedType(d) - XCTAssertTrue(type == .rssInJSON) - } - - // MARK: JSON Feed - - func testInessentialJSONFeedType() { - - let d = parserData("inessential", "json", "http://inessential.com/") - let type = feedType(d) - XCTAssertTrue(type == .jsonFeed) - } - - func testAllThisJSONFeedType() { - - let d = parserData("allthis", "json", "http://leancrew.com/allthis/") - let type = feedType(d) - XCTAssertTrue(type == .jsonFeed) - } - - func testCurtJSONFeedType() { - - let d = parserData("curt", "json", "http://curtclifton.net/") - let type = feedType(d) - XCTAssertTrue(type == .jsonFeed) - } - - func testPixelEnvyJSONFeedType() { - - let d = parserData("pxlnv", "json", "http://pxlnv.com/") - let type = feedType(d) - XCTAssertTrue(type == .jsonFeed) - } - - func testRoseJSONFeedType() { - - let d = parserData("rose", "json", "https://www.rosemaryorchard.com/") - let type = feedType(d) - XCTAssertTrue(type == .jsonFeed) - } - - // MARK: Unknown - - func testPartialAllThisUnknownFeedType() { - - // In the case of this feed, the partial data isn’t enough to detect that it’s a JSON Feed. - // The type detector should return .unknown rather than .notAFeed. - - let d = parserData("allthis-partial", "json", "http://leancrew.com/allthis/") - let type = feedType(d, isPartialData: true) - XCTAssertEqual(type, .unknown) - } - - // MARK: Performance - - func testFeedTypePerformance() { - - // 0.000 on my 2012 iMac. - - let d = parserData("EMarley", "rss", "https://medium.com/@emarley") - self.measure { - let _ = feedType(d) - } - } - - func testFeedTypePerformance2() { - - // 0.000 on my 2012 iMac. - - let d = parserData("inessential", "json", "http://inessential.com/") - self.measure { - let _ = feedType(d) - } - } - - func testFeedTypePerformance3() { - - // 0.000 on my 2012 iMac. - - let d = parserData("DaringFireball", "html", "http://daringfireball.net/") - self.measure { - let _ = feedType(d) - } - } - - func testFeedTypePerformance4() { - - // 0.001 on my 2012 iMac. - - let d = parserData("DaringFireball", "rss", "http://daringfireball.net/") - self.measure { - let _ = feedType(d) - } - } - -} - -func parserData(_ filename: String, _ fileExtension: String, _ url: String) -> ParserData { - let filename = "Resources/\(filename)" - let path = Bundle.module.path(forResource: filename, ofType: fileExtension)! - let data = try! Data(contentsOf: URL(fileURLWithPath: path)) - return ParserData(url: url, data: data) -} diff --git a/RSParser/Tests/RSParserTests/HTMLLinkTests.swift b/RSParser/Tests/RSParserTests/HTMLLinkTests.swift deleted file mode 100644 index 5817148d7..000000000 --- a/RSParser/Tests/RSParserTests/HTMLLinkTests.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// HTMLLinkTests.swift -// RSParser -// -// Created by Brent Simmons on 6/25/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import XCTest -import RSParser -import RSParserObjC - -class HTMLLinkTests: XCTestCase { - - func testSixColorsPerformance() { - - // 0.003 sec on my 2012 iMac - let d = parserData("sixcolors", "html", "http://sixcolors.com/") - self.measure { - let _ = RSHTMLLinkParser.htmlLinks(with: d) - } - } - - func testSixColorsLink() { - - let d = parserData("sixcolors", "html", "http://sixcolors.com/") - let links = RSHTMLLinkParser.htmlLinks(with: d) - - let linkToFind = "https://www.theincomparable.com/theincomparable/290/index.php" - let textToFind = "this week’s episode of The Incomparable" - - var found = false - for oneLink in links { - if let urlString = oneLink.urlString, let text = oneLink.text, urlString == linkToFind, text == textToFind { - found = true - } - } - - XCTAssertTrue(found) - XCTAssertEqual(links.count, 131) - } - -} diff --git a/RSParser/Tests/RSParserTests/HTMLMetadataTests.swift b/RSParser/Tests/RSParserTests/HTMLMetadataTests.swift deleted file mode 100644 index 5923dd72b..000000000 --- a/RSParser/Tests/RSParserTests/HTMLMetadataTests.swift +++ /dev/null @@ -1,154 +0,0 @@ -// -// HTMLMetadataTests.swift -// RSParser -// -// Created by Brent Simmons on 6/25/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import XCTest -import RSParser -import RSParserObjC - -class HTMLMetadataTests: XCTestCase { - - func testDaringFireball() { - - let d = parserData("DaringFireball", "html", "http://daringfireball.net/") - let metadata = RSHTMLMetadataParser.htmlMetadata(with: d) - - XCTAssertEqual(metadata.favicons.first?.urlString, "http://daringfireball.net/graphics/favicon.ico?v=005") - - XCTAssertEqual(metadata.feedLinks.count, 1) - - let feedLink = metadata.feedLinks.first! - XCTAssertNil(feedLink.title) - XCTAssertEqual(feedLink.type, "application/atom+xml") - XCTAssertEqual(feedLink.urlString, "http://daringfireball.net/feeds/main") - } - - func testDaringFireballPerformance() { - - // 0.002 sec on my 2012 iMac - let d = parserData("DaringFireball", "html", "http://daringfireball.net/") - self.measure { - let _ = RSHTMLMetadataParser.htmlMetadata(with: d) - } - } - - func testFurbo() { - - let d = parserData("furbo", "html", "http://furbo.org/") - let metadata = RSHTMLMetadataParser.htmlMetadata(with: d) - - XCTAssertEqual(metadata.favicons.first?.urlString, "http://furbo.org/favicon.ico") - - XCTAssertEqual(metadata.feedLinks.count, 1) - - let feedLink = metadata.feedLinks.first! - XCTAssertEqual(feedLink.title, "Iconfactory News Feed") - XCTAssertEqual(feedLink.type, "application/rss+xml") - } - - func testFurboPerformance() { - - // 0.001 sec on my 2012 iMac - let d = parserData("furbo", "html", "http://furbo.org/") - self.measure { - let _ = RSHTMLMetadataParser.htmlMetadata(with: d) - } - } - - func testInessential() { - - let d = parserData("inessential", "html", "http://inessential.com/") - let metadata = RSHTMLMetadataParser.htmlMetadata(with: d) - - XCTAssertNil(metadata.favicons.first?.urlString) - - XCTAssertEqual(metadata.feedLinks.count, 1) - let feedLink = metadata.feedLinks.first! - XCTAssertEqual(feedLink.title, "RSS") - XCTAssertEqual(feedLink.type, "application/rss+xml") - XCTAssertEqual(feedLink.urlString, "http://inessential.com/xml/rss.xml") - - XCTAssertEqual(metadata.appleTouchIcons.count, 0); - } - - func testInessentialPerformance() { - - // 0.001 sec on my 2012 iMac - let d = parserData("inessential", "html", "http://inessential.com/") - self.measure { - let _ = RSHTMLMetadataParser.htmlMetadata(with: d) - } - } - - func testCocoPerformance() { - - // 0.004 sec on my 2012 iMac - let d = parserData("coco", "html", "https://www.theatlantic.com/entertainment/archive/2017/11/coco-is-among-pixars-best-movies-in-years/546695/") - self.measure { - let _ = RSHTMLMetadataParser.htmlMetadata(with: d) - } - } - - func testSixColors() { - - let d = parserData("sixcolors", "html", "http://sixcolors.com/") - let metadata = RSHTMLMetadataParser.htmlMetadata(with: d) - - XCTAssertEqual(metadata.favicons.first?.urlString, "https://sixcolors.com/images/favicon.ico") - - XCTAssertEqual(metadata.feedLinks.count, 1); - let feedLink = metadata.feedLinks.first! - XCTAssertEqual(feedLink.title, "RSS"); - XCTAssertEqual(feedLink.type, "application/rss+xml"); - XCTAssertEqual(feedLink.urlString, "http://feedpress.me/sixcolors"); - - XCTAssertEqual(metadata.appleTouchIcons.count, 6); - let icon = metadata.appleTouchIcons[3]; - XCTAssertEqual(icon.rel, "apple-touch-icon"); - XCTAssertEqual(icon.sizes, "120x120"); - XCTAssertEqual(icon.urlString, "https://sixcolors.com/apple-touch-icon-120.png"); - } - - func testSixColorsPerformance() { - - // 0.002 sec on my 2012 iMac - let d = parserData("sixcolors", "html", "http://sixcolors.com/") - self.measure { - let _ = RSHTMLMetadataParser.htmlMetadata(with: d) - } - } - - func testCocoOGImage() { - - let d = parserData("coco", "html", "https://www.theatlantic.com/entertainment/archive/2017/11/coco-is-among-pixars-best-movies-in-years/546695/") - let metadata = RSHTMLMetadataParser.htmlMetadata(with: d) - let openGraphData = metadata.openGraphProperties - let image = openGraphData.images.first! - XCTAssert(image.url == "https://cdn.theatlantic.com/assets/media/img/mt/2017/11/1033101_first_full_length_trailer_arrives_pixars_coco/facebook.jpg?1511382177") - } - - func testCocoTwitterImage() { - - let d = parserData("coco", "html", "https://www.theatlantic.com/entertainment/archive/2017/11/coco-is-among-pixars-best-movies-in-years/546695/") - let metadata = RSHTMLMetadataParser.htmlMetadata(with: d) - let twitterData = metadata.twitterProperties - let imageURL = twitterData.imageURL! - XCTAssert(imageURL == "https://cdn.theatlantic.com/assets/media/img/mt/2017/11/1033101_first_full_length_trailer_arrives_pixars_coco/facebook.jpg?1511382177") - } - - func testYouTube() { - // YouTube is a special case — the feed links appear after the head section, in the body section. - let d = parserData("YouTubeTheVolvoRocks", "html", "https://www.youtube.com/user/TheVolvorocks") - let metadata = RSHTMLMetadataParser.htmlMetadata(with: d) - - XCTAssertEqual(metadata.feedLinks.count, 1); - let feedLink = metadata.feedLinks.first! - XCTAssertEqual(feedLink.title, "RSS"); - XCTAssertEqual(feedLink.type, "application/rss+xml"); - XCTAssertEqual(feedLink.urlString, "https://www.youtube.com/feeds/videos.xml?channel_id=UCct7QF2jcWRY6dhXWMSq9LQ"); - } -} diff --git a/RSParser/Tests/RSParserTests/Info.plist b/RSParser/Tests/RSParserTests/Info.plist deleted file mode 100644 index 6c40a6cd0..000000000 --- a/RSParser/Tests/RSParserTests/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - - diff --git a/RSParser/Tests/RSParserTests/JSONFeedParserTests.swift b/RSParser/Tests/RSParserTests/JSONFeedParserTests.swift deleted file mode 100644 index 459009c41..000000000 --- a/RSParser/Tests/RSParserTests/JSONFeedParserTests.swift +++ /dev/null @@ -1,124 +0,0 @@ -// -// JSONFeedParserTests.swift -// RSParser -// -// Created by Brent Simmons on 6/26/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import XCTest -import RSParser - -class JSONFeedParserTests: XCTestCase { - - func testInessentialPerformance() { - - // 0.001 sec on my 2012 iMac. - let d = parserData("inessential", "json", "http://inessential.com/") - self.measure { - let _ = try! FeedParser.parse(d) - } - } - - func testDaringFireballPerformance() { - - // 0.009 sec on my 2012 iMac. - let d = parserData("DaringFireball", "json", "http://daringfireball.net/") - self.measure { - let _ = try! FeedParser.parse(d) - } - } - - func testGettingFaviconAndIconURLs() { - - let d = parserData("DaringFireball", "json", "http://daringfireball.net/") - let parsedFeed = try! FeedParser.parse(d)! - - XCTAssert(parsedFeed.faviconURL == "https://daringfireball.net/graphics/favicon-64.png") - XCTAssert(parsedFeed.iconURL == "https://daringfireball.net/graphics/apple-touch-icon.png") - } - - func testAllThis() { - - let d = parserData("allthis", "json", "http://leancrew.com/allthis/") - let parsedFeed = try! FeedParser.parse(d)! - - XCTAssertEqual(parsedFeed.items.count, 12) - } - - func testCurt() { - - let d = parserData("curt", "json", "http://curtclifton.net/") - let parsedFeed = try! FeedParser.parse(d)! - - XCTAssertEqual(parsedFeed.items.count, 26) - - var didFindTwitterQuitterArticle = false - for article in parsedFeed.items { - if article.title == "Twitter Quitter" { - didFindTwitterQuitterArticle = true - XCTAssertTrue(article.contentHTML!.hasPrefix("

I’ve decided to close my Twitter account. William Van Hecke makes a convincing case")) - } - } - - XCTAssertTrue(didFindTwitterQuitterArticle) - } - - func testPixelEnvy() { - - let d = parserData("pxlnv", "json", "http://pxlnv.com/") - let parsedFeed = try! FeedParser.parse(d)! - XCTAssertEqual(parsedFeed.items.count, 20) - - } - - func testRose() { - let d = parserData("rose", "json", "http://www.rosemaryorchard.com/") - let parsedFeed = try! FeedParser.parse(d)! - XCTAssertEqual(parsedFeed.items.count, 84) - } - - func test3960() { - let d = parserData("3960", "json", "http://journal.3960.org/") - let parsedFeed = try! FeedParser.parse(d)! - XCTAssertEqual(parsedFeed.items.count, 20) - XCTAssertEqual(parsedFeed.language, "de-DE") - - for item in parsedFeed.items { - XCTAssertEqual(item.language, "de-DE") - } - } - - func testAuthors() { - let d = parserData("authors", "json", "https://example.com/") - let parsedFeed = try! FeedParser.parse(d)! - XCTAssertEqual(parsedFeed.items.count, 4) - - let rootAuthors = Set([ - ParsedAuthor(name: "Root Author 1", url: nil, avatarURL: nil, emailAddress: nil), - ParsedAuthor(name: "Root Author 2", url: nil, avatarURL: nil, emailAddress: nil) - ]) - let itemAuthors = Set([ - ParsedAuthor(name: "Item Author 1", url: nil, avatarURL: nil, emailAddress: nil), - ParsedAuthor(name: "Item Author 2", url: nil, avatarURL: nil, emailAddress: nil) - ]) - let legacyItemAuthors = Set([ - ParsedAuthor(name: "Legacy Item Author", url: nil, avatarURL: nil, emailAddress: nil) - ]) - - XCTAssertEqual(parsedFeed.authors?.count, 2) - XCTAssertEqual(parsedFeed.authors, rootAuthors) - - let noAuthorsItem = parsedFeed.items.first { $0.uniqueID == "Item without authors" }! - XCTAssertEqual(noAuthorsItem.authors, nil) - - let legacyAuthorItem = parsedFeed.items.first { $0.uniqueID == "Item with legacy author" }! - XCTAssertEqual(legacyAuthorItem.authors, legacyItemAuthors) - - let modernAuthorsItem = parsedFeed.items.first { $0.uniqueID == "Item with modern authors" }! - XCTAssertEqual(modernAuthorsItem.authors, itemAuthors) - - let bothAuthorsItem = parsedFeed.items.first { $0.uniqueID == "Item with both" }! - XCTAssertEqual(bothAuthorsItem.authors, itemAuthors) - } -} diff --git a/RSParser/Tests/RSParserTests/OPMLTests.swift b/RSParser/Tests/RSParserTests/OPMLTests.swift deleted file mode 100644 index a9f45f655..000000000 --- a/RSParser/Tests/RSParserTests/OPMLTests.swift +++ /dev/null @@ -1,79 +0,0 @@ -// -// OPMLTests.swift -// RSParser -// -// Created by Brent Simmons on 6/25/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import XCTest -import RSParser -import RSParserObjC - -class OPMLTests: XCTestCase { - - let subsData = parserData("Subs", "opml", "http://example.org/") - - func testOPMLParsingPerformance() { - - // 0.002 sec on my 2012 iMac. - self.measure { - let _ = try! RSOPMLParser.parseOPML(with: self.subsData) - } - } - - func testNotOPML() { - - let d = parserData("DaringFireball", "rss", "http://daringfireball.net/") - XCTAssertThrowsError(try RSOPMLParser.parseOPML(with: d)) - } - - func testSubsStructure() { - let opmlDocument = try! RSOPMLParser.parseOPML(with: subsData) - XCTAssertEqual("Subs", opmlDocument.title) - XCTAssertEqual("http://example.org/", opmlDocument.url) - recursivelyCheckOPMLStructure(opmlDocument) - } - - - func testFindingTitles() { - // https://github.com/brentsimmons/NetNewsWire/issues/527 - // Fix a bug where titles aren’t found when there’s no title attribute in the OPML, - // which appears to be true with OPML generated by The Old Reader. - - let d = parserData("SubsNoTitleAttributes", "opml", "http://example.org/") - let opmlDocument = try! RSOPMLParser.parseOPML(with: d) - recursivelyCheckOPMLStructure(opmlDocument) - } - -} - -private extension OPMLTests { - - func recursivelyCheckOPMLStructure(_ item: RSOPMLItem) { - let feedSpecifier = item.feedSpecifier - if !(item is RSOPMLDocument) { - XCTAssertNotNil((item.attributes! as NSDictionary).opml_text) - } - - // If it has no children, it should have a feed specifier. The converse is also true. - var isFolder = item.children != nil && item.children!.count > 0 - if !isFolder && (item.attributes! as NSDictionary).opml_title == "Skip" { - isFolder = true - } - - if !isFolder { - XCTAssertNotNil(feedSpecifier!.title) - XCTAssertNotNil(feedSpecifier!.feedURL) - } - else { - XCTAssertNil(feedSpecifier) - } - - if item.children != nil && item.children!.count > 0 { - for oneItem in item.children! { - recursivelyCheckOPMLStructure(oneItem) - } - } - } -} diff --git a/RSParser/Tests/RSParserTests/RSDateParserTests.swift b/RSParser/Tests/RSParserTests/RSDateParserTests.swift deleted file mode 100644 index c52310ac5..000000000 --- a/RSParser/Tests/RSParserTests/RSDateParserTests.swift +++ /dev/null @@ -1,108 +0,0 @@ -// -// RSDateParserTests.swift -// -// -// Created by Maurice Parker on 4/1/21. -// - -import Foundation -import XCTest -import RSParser - -class RSDateParserTests: XCTestCase { - - static func dateWithValues(_ year: Int, _ month: Int, _ day: Int, _ hour: Int, _ minute: Int, _ second: Int) -> Date { - var dateComponents = DateComponents() - dateComponents.calendar = Calendar.current - dateComponents.timeZone = TimeZone(secondsFromGMT: 0) - - dateComponents.year = year - dateComponents.month = month - dateComponents.day = day - dateComponents.hour = hour - dateComponents.minute = minute - dateComponents.second = second - - return dateComponents.date! - } - - func testDateWithString() { - var expectedDateResult = Self.dateWithValues(2010, 5, 28, 21, 3, 38) - - var d = RSDateWithString("Fri, 28 May 2010 21:03:38 +0000") - XCTAssertEqual(d, expectedDateResult) - - d = RSDateWithString("Fri, 28 May 2010 21:03:38 +00:00") - XCTAssertEqual(d, expectedDateResult) - - d = RSDateWithString("Fri, 28 May 2010 21:03:38 -00:00") - XCTAssertEqual(d, expectedDateResult) - - d = RSDateWithString("Fri, 28 May 2010 21:03:38 -0000") - XCTAssertEqual(d, expectedDateResult) - - d = RSDateWithString("Fri, 28 May 2010 21:03:38 GMT") - XCTAssertEqual(d, expectedDateResult) - - d = RSDateWithString("2010-05-28T21:03:38+00:00") - XCTAssertEqual(d, expectedDateResult) - - d = RSDateWithString("2010-05-28T21:03:38+0000") - XCTAssertEqual(d, expectedDateResult) - - d = RSDateWithString("2010-05-28T21:03:38-0000") - XCTAssertEqual(d, expectedDateResult) - - d = RSDateWithString("2010-05-28T21:03:38-00:00") - XCTAssertEqual(d, expectedDateResult) - - d = RSDateWithString("2010-05-28T21:03:38Z") - XCTAssertEqual(d, expectedDateResult) - - expectedDateResult = Self.dateWithValues(2010, 7, 13, 17, 6, 40) - d = RSDateWithString("2010-07-13T17:06:40+00:00") - XCTAssertEqual(d, expectedDateResult) - - expectedDateResult = Self.dateWithValues(2010, 4, 30, 12, 0, 0) - d = RSDateWithString("30 Apr 2010 5:00 PDT") - XCTAssertEqual(d, expectedDateResult) - - expectedDateResult = Self.dateWithValues(2010, 5, 21, 21, 22, 53) - d = RSDateWithString("21 May 2010 21:22:53 GMT") - XCTAssertEqual(d, expectedDateResult) - - expectedDateResult = Self.dateWithValues(2010, 6, 9, 5, 0, 0) - d = RSDateWithString("Wed, 09 Jun 2010 00:00 EST") - XCTAssertEqual(d, expectedDateResult) - - expectedDateResult = Self.dateWithValues(2010, 6, 23, 3, 43, 50) - d = RSDateWithString("Wed, 23 Jun 2010 03:43:50 Z") - XCTAssertEqual(d, expectedDateResult) - - expectedDateResult = Self.dateWithValues(2010, 6, 22, 3, 57, 49) - d = RSDateWithString("2010-06-22T03:57:49+00:00") - XCTAssertEqual(d, expectedDateResult) - - expectedDateResult = Self.dateWithValues(2010, 11, 17, 13, 40, 07) - d = RSDateWithString("2010-11-17T08:40:07-05:00") - XCTAssertEqual(d, expectedDateResult) - } - - func testAtomDateWithMissingTCharacter() { - let expectedDateResult = Self.dateWithValues(2010, 11, 17, 13, 40, 07) - let d = RSDateWithString("2010-11-17 08:40:07-05:00") - XCTAssertEqual(d, expectedDateResult) - } - - func testFeedbinDate() { - let expectedDateResult = Self.dateWithValues(2019, 9, 27, 21, 01, 48) - let d = RSDateWithString("2019-09-27T21:01:48.000000Z") - XCTAssertEqual(d, expectedDateResult) - } - -// func testHighMillisecondDate() { -// let expectedDateResult = Self.dateWithValues(2021, 03, 29, 10, 46, 56) -// let d = RSDateWithString("2021-03-29T10:46:56.516941+00:00") -// XCTAssertEqual(d, expectedDateResult) -// } -} diff --git a/RSParser/Tests/RSParserTests/RSSInJSONParserTests.swift b/RSParser/Tests/RSParserTests/RSSInJSONParserTests.swift deleted file mode 100644 index 266c0e19b..000000000 --- a/RSParser/Tests/RSParserTests/RSSInJSONParserTests.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// RSSInJSONParserTests.swift -// RSParser -// -// Created by Brent Simmons on 6/26/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import XCTest -import RSParser - -class RSSInJSONParserTests: XCTestCase { - - func testScriptingNewsPerformance() { - - // 0.003 sec on my 2012 iMac. - let d = parserData("ScriptingNews", "json", "http://scripting.com/") - self.measure { - let _ = try! FeedParser.parse(d) - } - } - - func testFeedLanguage() { - let d = parserData("ScriptingNews", "json", "http://scripting.com/") - let parsedFeed = try! FeedParser.parse(d)! - XCTAssertEqual(parsedFeed.language, "en-us") - } -} diff --git a/RSParser/Tests/RSParserTests/RSSParserTests.swift b/RSParser/Tests/RSParserTests/RSSParserTests.swift deleted file mode 100644 index 71384fa79..000000000 --- a/RSParser/Tests/RSParserTests/RSSParserTests.swift +++ /dev/null @@ -1,188 +0,0 @@ -// -// RSSParserTests.swift -// RSParser -// -// Created by Brent Simmons on 6/26/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import XCTest -import RSParser - -class RSSParserTests: XCTestCase { - - func testScriptingNewsPerformance() { - - // 0.004 sec on my 2012 iMac. - let d = parserData("scriptingNews", "rss", "http://scripting.com/") - self.measure { - let _ = try! FeedParser.parse(d) - } - } - - func testKatieFloydPerformance() { - - // 0.004 sec on my 2012 iMac. - let d = parserData("KatieFloyd", "rss", "http://katiefloyd.com/") - self.measure { - let _ = try! FeedParser.parse(d) - } - } - - func testEMarleyPerformance() { - - // 0.001 sec on my 2012 iMac. - let d = parserData("EMarley", "rss", "https://medium.com/@emarley") - self.measure { - let _ = try! FeedParser.parse(d) - } - } - - func testMantonPerformance() { - - // 0.002 sec on my 2012 iMac. - let d = parserData("manton", "rss", "http://manton.org/") - self.measure { - let _ = try! FeedParser.parse(d) - } - } - - func testNatashaTheRobot() { - - let d = parserData("natasha", "xml", "https://www.natashatherobot.com/") - let parsedFeed = try! FeedParser.parse(d)! - 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") - } - } - - func testTheOmniShowUniqueIDs() { - - let d = parserData("theomnishow", "rss", "https://theomnishow.omnigroup.com/") - let parsedFeed = try! FeedParser.parse(d)! - - for article in parsedFeed.items { - XCTAssertNotNil(article.uniqueID) - XCTAssertTrue(article.uniqueID.hasPrefix("https://theomnishow.omnigroup.com/episode/")) - } - } - - func testMacworldUniqueIDs() { - - // Macworld’s feed doesn’t have guids, so they should be calculated unique IDs. - - let d = parserData("macworld", "rss", "https://www.macworld.com/") - let parsedFeed = try! FeedParser.parse(d)! - - for article in parsedFeed.items { - XCTAssertNotNil(article.uniqueID) - XCTAssertEqual(article.uniqueID.count, 32) // calculated unique IDs are MD5 hashes - } - } - - func testMacworldAuthors() { - - // Macworld uses names instead of email addresses (despite the RSS spec saying they should be email addresses). - - let d = parserData("macworld", "rss", "https://www.macworld.com/") - let parsedFeed = try! FeedParser.parse(d)! - - for article in parsedFeed.items { - - let author = article.authors!.first! - XCTAssertNil(author.emailAddress) - XCTAssertNil(author.url) - XCTAssertNotNil(author.name) - } - } - - func testMonkeyDomGuids() { - - // https://coding.monkeydom.de/posts.rss has a bug in the feed (at this writing): - // It has guids that are supposed to be permalinks, per the spec — - // except that they’re not actually permalinks. The RSS parser should - // detect this situation, and every article in the feed should have a permalink. - - let d = parserData("monkeydom", "rss", "https://coding.monkeydom.de/") - let parsedFeed = try! FeedParser.parse(d)! - - for article in parsedFeed.items { - XCTAssertNil(article.url) - XCTAssertNotNil(article.uniqueID) - } - } - - func testEmptyContentEncoded() { - // The ATP feed (at the time of this writing) has some empty content:encoded elements. The parser should ignore those. - // https://github.com/brentsimmons/NetNewsWire/issues/529 - - let d = parserData("atp", "rss", "http://atp.fm/") - let parsedFeed = try! FeedParser.parse(d)! - - for article in parsedFeed.items { - XCTAssertNotNil(article.contentHTML) - } - } - - func testFeedKnownToHaveGuidsThatArentPermalinks() { - let d = parserData("livemint", "xml", "https://www.livemint.com/rss/news") - let parsedFeed = try! FeedParser.parse(d)! - for article in parsedFeed.items { - XCTAssertNil(article.url) - } - } - - func testAuthorsWithTitlesInside() { - // This feed uses atom authors, and we don’t want author/title to be used as item/title. - // https://github.com/brentsimmons/NetNewsWire/issues/943 - let d = parserData("cloudblog", "rss", "https://cloudblog.withgoogle.com/") - let parsedFeed = try! FeedParser.parse(d)! - for article in parsedFeed.items { - XCTAssertNotEqual(article.title, "Product Manager, Office of the CTO") - XCTAssertNotEqual(article.title, "Developer Programs Engineer") - XCTAssertNotEqual(article.title, "Product Director") - } - } - - func testTitlesWithInvalidFeedWithImageStructures() { - // This invalid feed has elements inside s. - // 17 Jan 2021 bug report — we’re not parsing titles in this feed. - let d = parserData("aktuality", "rss", "https://www.aktuality.sk/") - let parsedFeed = try! FeedParser.parse(d)! - for article in parsedFeed.items { - XCTAssertNotNil(article.title) - } - } - - func testFeedLanguage() { - let d = parserData("manton", "rss", "http://manton.org/") - let parsedFeed = try! FeedParser.parse(d)! - XCTAssertEqual(parsedFeed.language, "en-US") - } - -// func testFeedWithGB2312Encoding() { -// // This feed has an encoding we don’t run into very often. -// // https://github.com/Ranchero-Software/NetNewsWire/issues/1477 -// let d = parserData("kc0011", "rss", "http://kc0011.net/") -// let parsedFeed = try! FeedParser.parse(d)! -// XCTAssert(parsedFeed.items.count > 0) -// for article in parsedFeed.items { -// XCTAssertNotNil(article.contentHTML) -// } -// } -} diff --git a/RSParser/Tests/RSParserTests/Resources/3960.json b/RSParser/Tests/RSParserTests/Resources/3960.json deleted file mode 100644 index 1376f93b2..000000000 --- a/RSParser/Tests/RSParserTests/Resources/3960.json +++ /dev/null @@ -1,642 +0,0 @@ -{ - "version": "https://jsonfeed.org/version/1.1", - "title": "fboës - Der Blog | Startseite", - "home_page_url": "https://journal.3960.org/", - "feed_url": "https://journal.3960.org/feed.json", - "description": "Programmierung, Luft- & Raumfahrt, Kurioses: Der Blog von und mit Frank Boës.", - "icon": "https://cdn.3960.org/favicon-192x192.png", - "favicon": "https://cdn.3960.org/images/tile-128x128.png", - "author": { - "name": "Frank Boës", - "url": "mailto:info@3960.org" - }, - "authors": [ - { - "name": "Frank Boës", - "url": "mailto:info@3960.org" - } - ], - "language": "de-DE", - "_rss": { - "about": "http://cyber.harvard.edu/rss/rss.html", - "copyright": "© 2008-2020 Creative Commons BY" - }, - "items": [ - { - "id": "user/posts/2020-02-21-lecker-lecker/index.md", - "url": "https://journal.3960.org/posts/2020-02-21-lecker-lecker/", - "title": "Lecker, lecker", - "content_html": "

Da ist es zwar nicht ganz billig,
dafür schmeckt's aber auch nicht.

\"\"", - "summary": "Da ist es zwar nicht ganz billig, dafür schmeckt's aber auch nicht.", - "date_published": "2020-02-21T18:08:06+01:00", - "date_modified": "2020-02-21T18:08:06+01:00", - "author": { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - }, - "authors": [ - { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - } - ], - "banner_image": "https://cdn.3960.org/favicon-192x192.png", - "language": "de-DE", - "image": "https://cdn.3960.org/favicon-192x192.png", - "tags": [ - "Lustiges" - ] - }, - { - "id": "user/posts/2020-02-10-sabine-flughafen-amsterdam/index.md", - "url": "https://journal.3960.org/posts/2020-02-10-sabine-flughafen-amsterdam/", - "title": "„Sabine“ am Flughafen Amsterdam", - "content_html": "

METAR-Informationen sind nicht nur für die Fliegerei praktische und kompakte Möglichkeiten, Wetterbedingungen zusammenzufassen. Hier braust zum Beispiel am 09. Januar 2020 das Sturmtief „Sabine“ mit Windgeschwindigkeiten bis zu 51 Knoten über den Flughafen Amsterdam-Schiphol:

\n
EHAM 091725Z 20037G51KT 170V230 9999 FEW011 BKN014 BKN025 11/11 Q0986 RE/RA TEMPO 7000
\n

Solch einen METAR-Code kann z.B. mit dem Aerofly FS2 Wettergerät verwendet werden.

\"\"", - "summary": "METAR-Informationen sind nicht nur für die Fliegerei praktische und kompakte Möglichkeiten, Wetterbedingungen zusammenzufassen. Hier braust zum Beispiel am 09…", - "date_published": "2020-02-10T18:40:04+01:00", - "date_modified": "2020-02-11T08:51:21+01:00", - "author": { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - }, - "authors": [ - { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - } - ], - "banner_image": "https://cdn.3960.org/favicon-192x192.png", - "language": "de-DE", - "image": "https://cdn.3960.org/favicon-192x192.png", - "tags": [ - "Fliegerei", - "Simulation" - ] - }, - { - "id": "user/posts/2020-01-19-migrants/index.md", - "url": "https://journal.3960.org/posts/2020-01-19-migrants/", - "title": "Migrants", - "content_html": "
\"\"", - "summary": "", - "date_published": "2020-01-19T18:09:37+01:00", - "date_modified": "2020-01-19T18:09:37+01:00", - "author": { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - }, - "authors": [ - { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - } - ], - "banner_image": "https://img.youtube.com/vi/G2dGWH90aew/hqdefault.jpg", - "language": "de-DE", - "image": "https://img.youtube.com/vi/G2dGWH90aew/hqdefault.jpg", - "tags": [ - "Geckobar", - "Raumfahrt", - "The Cool" - ] - }, - { - "id": "user/posts/2019-12-30-ssh-schluessel-unter-windows-komplett/index.md", - "url": "https://journal.3960.org/posts/2019-12-30-ssh-schluessel-unter-windows-komplett/", - "title": "SSH-Schlüssel unter Windows – das komplette Programm", - "content_html": "

\"\" SSH-Schlüssel sind die Eintrittskarte für SSH, Git, und viele andere darauf aufbauende, nützliche Dienste, die ein Programmierer nutzen möchte. Während unter Linux und Mac OSX das Erzeugen eines SSH-Schlüssels eine schmerzlose Sache ist, sind unter Windows mehr Handgriffe gefragt, um alle Anwendungsfälle eines SSH-Schlüssels abbilden zu können – unter anderem für die Verwendung des selben Schlüssels aus einer Virtualisierung wie zum Beispiel VirtualBox oder Docker.

\n\n

Generell gibt es zwei Arten, unter Windows einen SSH-Schlüssel zu speichern:

\n
    \n
  • Das PuTTY Private Key-Format (ppk)
  • \n
  • Das unter UNIX bekannte OpenSSH-Format mit zwei Dateien für den privaten und öffentlichen Teil des Schlüssels
  • \n
\n

Viele Anleitungen beschränken sich auf die eine oder andere Methode zum Anlegen von SSH-Schlüsseln. Tatsächlich macht es aber unter Windows sehr viel Sinn, den eigenen Schlüssel in beiden Formate verfügbar zu haben. Das geht deutlich schmerzloser, wenn beide Schlüsselformate in einem Aufwasch angelegt werden.

\n
    \n
  1. Zuallererst werden die PuTTY Utilities installiert – diese beinhalten neben PuTTY auch den puttygen Schlüsselgenerator, pageant Schlüsselagenten und das plink SSH-Verbindungstool.
  2. \n
  3. Ebenfalls wird Git für Windows benötigt – hier ist wiederum Git Bash enthalten, einen Linux-Bash-Emulator.
  4. \n
  5. Im nächsten Schritt muss Git Bash geöffnet werden und im eigenen Benutzerverzeichnis das SSH-Schlüsselverzeichnis mittels mkdir -p ~/.ssh angelegt werden.
  6. \n
  7. Jetzt wird PuTTYGen gestartet, um in dem just angelegten Verzeichnis (unter C:\\Users\\USERNAME\\.ssh) die insgesamt drei Dateien anzulegen, die zusammen alle Schlüssel-Komponenten darstellen.
  8. \n
\n

PuTTYGen

\n

In PuttyGen selber wird es nun etwas trickreich:

\n
    \n
  1. Als „Key comment“ gibt wird die eigene E-Mail-Adresse verwendet. Das hilft später bei der Identifikation des Schlüssels.
  2. \n
  3. Außerdem sollte eine Passphrase vergeben werden, die den Schlüssel schützt, falls die Schlüsseldateien abhanden kommen sollten.
  4. \n
  5. Nicht zuletzt sollte bei der Bitlänge des Schlüssel entweder 2048 oder aber gleich 4096 verwendet werden.
  6. \n
  7. Nun drückt ihr den Knopf „Generate“ und wischt mit der Maus über dem PuTTYGen-Fenster hin und her, um ein Zufallsmuster zu erzeugen. Im Anschluss hat das Programm einen Schlüssel generiert, den ihr wie folgt speichern müsst:
  8. \n
  9. Die PPK-Datei mittels „File → Save private key“ in C:\\Users\\USERNAME\\.ssh\\id_rsa.ppk speichern.
  10. \n
  11. Aus der oberen Textbox von PuTTYGen den „OpenSSH Public Key“ kopieren und in eine neue Datei in C:\\Users\\USERNAME\\.ssh\\id_rsa.pub einfügen (mit der Funktion „Save public key“ wird der Public Key nicht im korrekten Format exportiert).
  12. \n
  13. Mit „Conversions → Export OpenSSH key“ den Private Key unter C:\\Users\\USERNAME\\.ssh\\id_rsa speichern. Wichtig ist hierbei, dass keine Dateiendung verwendet wird.
  14. \n
\n

In eurem SSH-Verzeichnis C:\\Users\\USERNAME\\.ssh sollten nun mindestens drei Dateien liegen:

\n
id_rsa.ppk # Privater / öffentlicher Schlüssel für PuTTY\nid_rsa.pub # Öffentlicher Schlüssel für OpenSSH\nid_rsa     # Privater Schlüssel für OpenSSH
\n

Später werden in diesem Verzeichnis ggf. noch mehr Dateien auftauchen, wie z.B. known_hosts, authorized_keys und config.

\n

Übrigens solltet ihr mit der Git Bash mittels chmod 644 ~/.ssh/* && chmod 600 ~/.ssh/id_rsa allen Dateien die korrekten Zugriffsrechte (-rw-r--r-- bzw. -rw-------) geben, falls dies nicht schon geschehen ist.

\n

Verbindung, bitte

\n

Den öffentlichen Schlüssel könnt ihr nun auf allen Servern und Diensten bekannt machen, mit denen ihr euch später verbinden wollt. Auf eurem Rechner wiederum wird die Git Bash automatisch den privaten OpenSSH-Schlüssel verwenden, während andere Programme den privaten Schlüssel aus der id_rsa.ppk entweder in den Einstellungen übergeben bekommen müssen, oder aber aus einem vorher zu startenden pageant Schlüsselagenten übernehmen können. Falls ihr SSH-Schlüssel für eure tägliche Arbeit benutzt, könnt ihr den Pageant auch automatisch starten und den Schlüssel laden lassen.

\n

Viele Windows-Programme können so euren SSH-Schlüssel nutzen, u.a.:

\n\n

So oder so seid ihr nun mit beiden Schlüsselformaten ausgestattet, so dass z.B. auch der Wechsel des Betriebssystems oder die Verwendung von Virtualisierung euch die Weiterbenutzung eures SSH-Schlüssels erlaubt.

\n

Unter Linux und Mac OSX ist der Vorgang übrigens etwas kürzer:

\n
ssh-keygen -t rsa -b 4096 -C "YOUR@EMAIL"
\"\"", - "summary": "SSH-Schlüssel sind die Eintrittskarte für SSH, Git, und viele andere darauf aufbauende, nützliche Dienste, die ein Programmierer nutzen möchte. Während unter…", - "date_published": "2019-12-30T18:52:10+01:00", - "date_modified": "2020-01-11T10:00:49+01:00", - "author": { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - }, - "authors": [ - { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - } - ], - "banner_image": "https://journal.3960.org/posts/2019-12-30-ssh-schluessel-unter-windows-komplett/puttygen.png", - "language": "de-DE", - "image": "https://journal.3960.org/posts/2019-12-30-ssh-schluessel-unter-windows-komplett/puttygen.png", - "tags": [ - "Für Tumblr", - "Programmierung", - "Webdevelop", - "Git", - "Bash", - "Anleitung" - ] - }, - { - "id": "user/posts/2019-12-22-firefox-weniger-werbung-mehr-speed-unter-android/index.md", - "url": "https://journal.3960.org/posts/2019-12-22-firefox-weniger-werbung-mehr-speed-unter-android/", - "title": "Firefox – Weniger Werbung, mehr Speed unter Android", - "content_html": "

\"\" Werbung nervt. Tracking nervt. Nerven nervt. Und gerade mobil habe ich eigentlich keine Zeit, meine geringe Download-Rate mit dem Herunterladen von hässlichen Werbemitteln zu verbringen. Aber welche Optionen hat man schon auf einem Smartphone?

\n\n

Schon vor einiger Zeit war ich auf Firefox für Android gestoßen. Da ich schon immer ein Herz für Mozilla hatte und Firefox einer der wenigen Browser unter Android war, in dem ein AdBlocker funktionierte, war ich von Chrome für Android auf Firefox für Android umgestiegen.

\n

Oh, ein Nerd-Telefon! Android, Firefox und DuckDuckGo.

\n

Ein Wermutstropfen hatte Firefox für Android aber: Im Vergleich zu Android-Chrome war er nicht wirklich schnell. Das fiel augenscheinlich auch Mozilla auf, so dass sich in der stetig vergrößernden Palette an Mozillas Android-Apps eine neue Version von Firefox für Android findet: Mozilla Firefox Preview.

\n

Von seinem Vorgänger unterscheidet Firefox Preview neben einer etwas kompakteren Oberfläche vor allen Dingen seine rasante Geschwindigkeit. Und das man (aktuell) keine Plugins installieren kann ist sofort vergessen, denn der eingebaute AdBlocker (Enhanced Tracking Protection bzw. ETP genannt) funktioniert einfach fantastisch (so das meine Bastelei für den FritzBox-AdBlocker für diesen Browser überflüssig wird).

\n

\"NeulichNeulich auf einer ganz normalen Nachrichtenseite.
Nebenbei kann der Firefox Preview auch den Webview auf Android ersetzen. Damit sind alle Apps auf dem Telefon, die Webview verwenden, nicht nur mit der Rendering-Engine sondern auch mit dem AdBlocker von Firefox Preview ausgestattet.

\n

Nicht zuletzt ist in Firefox Preview der Dienst „Firefox Sync“ eingebaut. Sobald man sich einen kostenlosen Firefox Account zugelegt hat, können alle anderen mit diesem Account registrierten Firefox-Browser Historie und Lesezeichen teilen. Damit ist das Erlebnis wie bei Google Chrome mit aktivierten Google Account.

\n

Übrigens: Mozillas Bemühen um neue Privatsphären-Services hat Firefox Monitor hervorgebracht. Mit einem Firefox-Account kann man sich so informieren lassen, ob die eigene E-Mail-Adresse von einem Datenleck betroffen ist. Ein sinnvoller Service, der mich zum Beispiel darauf gebracht hat, dass einige von mir benutzte Dienste meine E-Mail-Adresse nebst Passwort-Hash verloren hatten – und merkwürdigerweise ein nicht von mir benutzter Dienst, womit sich hier möglicherweise der Kreis zum Trackingschutz von Mozilla Firefox schließt. 😉

\"\"", - "summary": "Werbung nervt. Tracking nervt. Nerven nervt. Und gerade mobil habe ich eigentlich keine Zeit, meine geringe Download-Rate mit dem Herunterladen von hässlichen…", - "date_published": "2019-12-22T18:33:43+01:00", - "date_modified": "2020-01-07T10:31:04+01:00", - "author": { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - }, - "authors": [ - { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - } - ], - "banner_image": "https://journal.3960.org/posts/2019-12-22-firefox-weniger-werbung-mehr-speed-unter-android/firefox.png", - "language": "de-DE", - "image": "https://journal.3960.org/posts/2019-12-22-firefox-weniger-werbung-mehr-speed-unter-android/firefox.png", - "tags": [ - "Für Tumblr", - "Geckobar", - "Review", - "Technologie", - "Webdevelop", - "Adblocker" - ] - }, - { - "id": "user/posts/2019-10-27-event-handling-mit-javascript-ohne-jquery/index.md", - "url": "https://journal.3960.org/posts/2019-10-27-event-handling-mit-javascript-ohne-jquery/", - "title": "Event-Handling mit JavaScript – und ohne jQuery", - "content_html": "

\"\" Als Web-Entwickler fügen wir im Laufe eines Projektes einer Website eine zumeist nicht unerhebliche Anzahl an JavaScript-Event-Handlern hinzu – sei es mit jQuery oder regulärem JavaScript (You Might Not Need jQuery). Abhängig von der gewählten Methode lässt sich damit… die Performance einer Website gründlich ruinieren.

\n

Aber das muss nicht sein – wie dieser Überblick über die Montage von Event-Handlern in JavaScript / jQuery zeigt.

\n\n

Ganz grundsätzlich muss jeder Prozessschritt beim Hinzufügen eines Event-Handlers richtig angewendet werden. Der ganze Vorgang besteht aus drei Schritten:

\n
    \n
  1. Die DOM-Elemente selektieren,
  2. \n
  3. auf dem selektierten DOM-Elementen einen Event-Typ beobachten,
  4. \n
  5. und schließlich bei Auslösen des Events einen Event-Listener ausführen.
  6. \n
\n

In jedem dieser Schritte lässt sich zum Teil massiv optimieren.

\n

Kenne deine Selektoren

\n

Um einen Event-Handler montieren zu können, muss dieser an ein Element angekoppelt werden – in der Regel ist dies ein DOM-Element. Dazu gibt es verschiedene Methoden, DOM-Elemente zu selektieren. Je nach gewählter Methode ist dies mehr oder weniger performant.

\n

Faustformel: Je eindeutiger das Suchmerkmal und je kleiner die Menge der zu durchsuchenden Elemente, desto schneller ist die Suche.

\n

Die schnellste Methode ist dabei die Selektion über ein id-Attribut – die langsamste dagegen die Suche nach einem beliebigen Attribut, im schlimmsten Fall mit der Prüfung, ob dieses Attribut einen bestimmten Wert beinhaltet.

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
DOM-Selektions-Methoden, sortiert nach Performance
MethodejQueryJavaScriptNeues JavaScript
ID$('#x')document.getElementById('x')document.querySelector('#x')
Klasse$('.x')document.getElementsByClassName('x')document.querySelectorAll('.x')
Tag$('x')document.getElementsByTagName('x')document.querySelectorAll('x')
Attribut$('[x]')n/adocument.querySelectorAll('[x]')
CSS (s.u.)$('x y')n/adocument.querySelectorAll('x y')
\n

Sowohl die jQuery-Methode $(…) als auch die JavaScript-Methoden .querySelector() / .querySelectorAll() unterstützen die Auswahl per CSS-Selektor. Damit können auch kompliziertere Suchen im DOM durchgeführt werden, wie z.B. mit nav a das Auffinden aller <a> in einem <nav>. Zum Glück ist die Browser-Unterstützung für .querySelector() / .querySelectorAll() inzwischen sehr gut.

\n

Zu beachten ist, dass die JavaScript-Methoden .querySelector() / .querySelectorAll() je nach Browser um ein Mehrfaches langsamer sind als ihre „einfachen“ Geschwister .getElementById, .getElementsByClassName und .getElementsByTagName.

\n

Mengenlehre: Selektieren in der Selektion

\n

Interessanterweise kann jede Methode nicht nur auf das gesamte Dokument angewendet werden, sondern auf eine bereits bestehende Selektion. Damit kann die Suche stark beschleunigt werden.

\n

In jQuery existiert dafür die .find()-Methode, die analog zu .on() funktioniert…

\n
var navigation = $('nav');\nvar navLinks = navigation.find('a');\nvar navBolds = navigation.find('b');
\n

…in regulärem JavaScript bleiben die Methoden identisch zu den für die Suche im Dokument verfügbaren Methoden:

\n
var navigation = document.querySelector('nav');\nvar navLinks = navigation.querySelectorAll('a');\nvar navBolds = navigation.querySelectorAll('b');
\n

Diese Methode kann sehr hilfreich sein, wenn später sowieso DOM-Manipulation an übergeordneten DOM-Elementen notwendig werden.

\n

Zu beachten ist bei regulärem JavaScript, dass die Methoden .getElementById() und .querySelector() ein einzelnes Element bzw. einen einzelnen Node (d.h. ein DOM-Element) zurückgeben, während alle anderen Selektions-Methoden eine HTMLCollection bzw. NodeList zurückgeben, die vereinfacht gesagt Arrays von Nodes sind.

\n

Event-Handler hinzufügen

\n

Für das Hinzufügen von Event-Listenern bietet jQuery die Methode .on(), und JavaScript die Methode .addEventListener() an. (Wir ignorieren die Methoden zum Hinzufügen von Event-Handlern direkt via HTML-Attribut, da dadurch eine unglückliche Verkettung von Content (HTML) und Verhalten (JavaScript) entsteht.)

\n

In beiden Fällen verfügt die Selektion über eine Methode, der man nur den Event-Typ und den eigentlichen Event-Listener übergeben muss.

\n
$('nav').on('click', function() {\n  $(this).addClass('active');\n})
\n

Der selbe Aufruf ist in Vanilla-JavaScript etwas mehr Schreibarbeit, aber ansonsten identisch:

\n
document.querySelector('nav').addEventListener('click', function(event) {\n  event.target.classList.add('active');\n});
\n

Zu beachten in JavaScript: Event-Listener können nur einem einzelnen DOM-Element hinzugefügt werden – jQuery erlaubt es, am Stück mehreren DOM-Elementen ein und denselben Event-Listener hinzuzufügen.

\n

In beiden Fällen steht im Event-Listener mit $(this) bzw. event.target das DOM-Element direkt zur Verfügung, auf dem das Event ausgelöst wurde. Voraussetzung ist bei JavaScript, dass der Event-Listener als ersten Parameter eine Variable namens event gesetzt bekommen hat.

\n

Sieben Event-Typen auf einen Streich…

\n

Ein nicht unwahrscheinlicher Anwendungsfall ist, verschiedene Event-Typen mit dem selben Event-Handler bedienen zu wollen. In jQuery kann man an die .on()-Methode eine Liste an verschiedenen Event-Typen übergeben:

\n
// Fires on `click` `keyup` `blur`\n$('nav').on('click keyup blur', function() {\n  $(this).addClass('active');\n})
\n

In JavaScript ist ein bisschen mehr Gehirnschmalz notwendig, denn hier müssen wir jeden Event-Listener mit einem einzelnen Aufruf hinzufügen. Das könnte man in einer Schleife tun…

\n
// Bad example: Fires on `click` `keyup` `blur`\n['click', 'keyup', 'blur'].forEach(function(eventType) {\n  document.querySelector('nav').addEventListener(eventType, function(event) {\n    event.target.classList.add('active');\n  });\n});
\n

…und handelt sich auf diese Weise zwei Performance-Killer ein: Einerseits wird in jedem Schleifendurchlauf document.querySelector neu ausgewertet, andererseits wird jedes Mal Speicher für eine neue, anonyme Funktion reserviert. Glücklicherweise kann man beide Konstruktionen aus dem Schleifenkörper herausziehen:

\n
// Better example: Fires on `click` `keyup` `blur`\nvar eventTarget = document.querySelector('nav');\nvar eventListener = function(event) {\n  event.target.classList.add('active');\n};\n\n['click', 'keyup', 'blur'].forEach(function(eventType) {\n  eventTarget.addEventListener(eventType, eventListener);\n});
\n

Das Array abgerollt sieht dann sogar noch übersichtlicher aus, und zeigt plastisch den Vorteil der vorherigen Deklaration von Event-Ziel und -Listener:

\n
// Best example for readability: Fires on `click` `keyup` `blur`\nvar eventTarget = document.querySelector('nav');\nvar eventListener = function(event) {\n  event.target.classList.add('active');\n};\n\neventTarget.addEventListener('click', eventListener);\neventTarget.addEventListener('keyup', eventListener);\neventTarget.addEventListener('blur', eventListener);
\n

Weniger ist… weniger

\n

Wenn sich auf einer Seite mehrere DOM-Objekte befinden, die wir mit einem identischen Event-Handler ausstatten wollen, so gibt es in jQuery die folgende Methode.

\n
// Add Event Handler to all `.btn`\n$('.btn').on('click', function() {\n  $(this).addClass('active');\n})
\n

Besonders spannend: Bei .on() und .querySelectorAll() können auch mehrere CSS-Selektoren, durch Kommata getrennt, gleichzeitig abgefragt werden. Mit header a, footer a kriegt man eine Liste aller <a> in <header> und <footer> zurück.

\n

In Vanilla-JavaScript wird das Hinzufügen zu Event-Listenern zu mehreren DOM-Elementen etwas umständlicher, weil ein Event-Handler immer nur einem DOM-Objekt hinzugefügt werden kann. Wenn wir also eine Liste von DOM-Objekten haben, müssen wir jedem DOM-Objekt beim Durchlaufen einer Schleife einen Handler verpassen:

\n
// Add Event Handler to all `.btn`\ndocument.querySelectorAll('.btn').forEach(function(btn) {\n  btn.addEventListener('click', function(event) {\n    event.target.classList.add('active');\n  });\n});
\n

Warum ist das in JavaScript eigentlich so deutlich weniger bequem? Der Grund ist ganz einfach: Diese Konstruktion hat massive Performance-Auswirkungen. Wir fügen damit eine größere Anzahl von Event-Handlern hinzu, die alle das Gleiche tun, aber unterschiedliche DOM-Objekte beobachten müssen. Damit muss der Browser mehr Dinge beobachten. Bei einer 10×10 Zellen umfassenden Tabelle kann ein Event-Listener für eine Tabellenzelle also auf insgesamt 100 Events verteilt werden.

\n

Für diesen Fall bietet jQuery eine performante Alternative: Statt jedes Element einzeln mit einem Event-Handler auszustatten, wird einfach ein DOM-Objekt ausgewählt, dass im DOM oberhalb der zu beobachtenden DOM-Objekte liegt. Dieses übergeordnete Objekt wird über alle Events informiert, die unterhalb von ihm stattfinden – das sogenannte „Event Bubbling“.

\n

In jQuery wird der Filter für das eigentliche Event-Ziel als zusätzlicher Parameter von .on() übergeben.

\n
// Add Event Handler to `nav`, fire if `.btn` was clicked\n$('nav').on('click', '.btn', function() {\n  $(this).addClass('active');\n})
\n

In Vanilla-JavaScript wird das Event-Ziel innerhalb des Event-Listeners gefiltert:

\n
// Add Event Handler to `nav`, fire if `.btn` was clicked\ndocument.querySelector('nav').addEventListener('click', function(event) {\n  if (event.target.matches('.btn')) {\n    event.target.classList.add('active');\n  }\n});
\n

Bezogen auf unser Beispiel mit den 100 Tabellenzellen haben wir gerade aus 100 Event-Handlern einen einzigen Event-Handler gemacht. Das spart nicht nur Speicher, sondern erlaubt es auch, auf DOM-Elemente zu reagieren, die beim Hinzufügen des Event-Handlers noch gar nicht im DOM existierten. Wenn zum Beispiel zu unserem <nav>-Element erst nach dem Hinzufügen des Event-Handlers neue <… class=„btn“> zum Beispiel via AJAX hinzugefügt werden, wird unser Event-Handler auf dem <nav> auch diese Elemente mit verarbeiten, da er ja auf alle Elemente unterhalb von ihm reagiert.

\"\"", - "summary": "Als Web-Entwickler fügen wir im Laufe eines Projektes einer Website eine zumeist nicht unerhebliche Anzahl an JavaScript-Event-Handlern hinzu – sei es mit…", - "date_published": "2019-10-27T17:01:02+01:00", - "date_modified": "2020-02-09T17:01:08+01:00", - "author": { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - }, - "authors": [ - { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - } - ], - "banner_image": "https://journal.3960.org/posts/2019-10-27-event-handling-mit-javascript-ohne-jquery/event.png", - "language": "de-DE", - "image": "https://journal.3960.org/posts/2019-10-27-event-handling-mit-javascript-ohne-jquery/event.png", - "tags": [ - "Javascript", - "Programmierung", - "Webdevelop" - ] - }, - { - "id": "user/posts/2019-10-18-kalorien-sind-auch-option-stipp-isch/index.md", - "url": "https://journal.3960.org/posts/2019-10-18-kalorien-sind-auch-option-stipp-isch/", - "title": "Kalorien sind auch eine Option: Stipp(isch)", - "content_html": "

Es gibt Gerichte, die trotz allem schlechten Gewissen oder entgegen ärztlichen Ratschlägen immer wieder gerne auf den Tisch kommen. Eines davon ist Stipp(isch) – ein Gericht, das in jüngster Zeit wieder neue Fans gefunden hat. Höchste Zeit also, das Rezept zu verraten.

\n\n

Stipp(isch) ist eigentlich nur eine äußerst leckere Sauce, die aus Alibi-Gründen über ein paar Kartoffeln gekippt wird. Die Einkaufsliste für diese Operation ist relativ handlich. Ihr benötigt, um 2–3 Personen glücklich zu machen:

\n
    \n
  • 300g gewürfelter Schinkenspeck,
  • \n
  • 3 große Zwiebeln,
  • \n
  • 400ml Sahne (ihr habt einen Eindruck, wohin die Reise geht)
  • \n
  • etwas Milch nach Gefühl
  • \n
  • sowie Pfeffer, Salz
  • \n
  • und bei Bedarf Fondor.
  • \n
  • Dazu passen gut Pellkartoffeln.
  • \n
\n

Kurz zusammengefasst besteht die Sauce also aus Speck, Zwiebeln und Sahne.

\n

Die Zubereitung ist fast genau so einfach:

\n
    \n
  1. In einer großen Pfanne die Schinkenwürfel anbraten.
  2. \n
  3. Die gewürfelten Zwiebeln hinzugeben und anbraten, bis sie glasig werden.
  4. \n
  5. In der Zwischenzeit kann man bereits die Kartoffeln in einem separaten Topf auf den Herd stellen.
  6. \n
  7. Darüber Sahne und ggf. Milch geben – die Konsistenz der Sauce sollte dabei leicht zähflüssig bleiben.
  8. \n
  9. Anständig würzen, gut verrühren – kurz vor dem Aufkochen ist die Sauce dann fertig.
  10. \n
\n

Die Sauce kann durch harte körperliche (oder seelische Arbeit) verlorene Kalorie relativ flott wiederherstellen. 😉

\"\"", - "summary": "Es gibt Gerichte, die trotz allem schlechten Gewissen oder entgegen ärztlichen Ratschlägen immer wieder gerne auf den Tisch kommen. Eines davon ist Stipp(isch…", - "date_published": "2019-10-18T18:55:49+02:00", - "date_modified": "2019-12-04T12:40:29+01:00", - "author": { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - }, - "authors": [ - { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - } - ], - "banner_image": "https://cdn.3960.org/favicon-192x192.png", - "language": "de-DE", - "image": "https://cdn.3960.org/favicon-192x192.png", - "tags": [ - "Rezept" - ] - }, - { - "id": "user/posts/2019-10-14-kalaschnikow-programmierung/index.md", - "url": "https://journal.3960.org/posts/2019-10-14-kalaschnikow-programmierung/", - "title": "Kalaschnikow-Programmierung", - "content_html": "

Dies ist meine Philosophie über Programmierung. Es gibt viele davon, aber diese hier ist meine.

\n\n

Jeder Programmierer entscheidet mit jeder Zeile, die er oder sie schreibt, wie er oder sie ein Problem lösen möchte. Verwirrenderweise gibt es dabei oft keinen „richtigen“ oder „falschen“ Weg (ausgenommen natürlich von handwerklichen Fehler, die absolut betrachtet falsch sind). Viel mehr entwickelt jeder Programmierer eine Meinung, warum bestimmte Wege besser sind als andere. Diese Meinung wiederum basiert auf Attributen, die einem wichtig erscheinen.

\n

Kalaschnikow?

\n

Die Sturmgewehr-Familie, die mit der Kalaschnikow AK-47 begründet wurde, erfreut sich auch nach über siebzig Jahren Produktion immer noch einer überragenden Verbreitung. Im Gegensatz zu deutlich moderneren, fortschrittlicheren Konkurrenzmodellen überrascht die Langlebigkeit dieses Produktlinie – und die Anzahl der Kopien und Derivate, die seitdem entwickelt wurden.

\n

Der AK-47 werden viele Dinge nachgesagt: Sie sei günstig, einfach zu produzieren, robust, geht tolerant mit Fehlbehandlung um und kann trotzdem gute Ergebnisse erzielen. Sie kann auch von wenig versierten Personen benutzt und repariert werden. Wie viele Konstruktionen aus sowjet-russischer Produktion ist die AK-47 unverwüstlich.

\n

Unabhängig davon, wofür eine Waffe also solche und diese im Speziellen steht, kann man von diesem Gewehr eine Menge für die Programmierung lernen.

\n

Kalaschnikow-Programmierung

\n

Für gute Programmierung existieren Unmengen an ausformulierten best practices, Coding-Standards oder Anleitungen. Diese im Detail zu kennen ist aber gar nicht so notwendig, wenn hinter jeder Entscheidung bei der Programmierung eine konsistente Philosophie steckt – wie zum Beispiel die Kalaschnikow-Programmierung.

\n

Im Detail sind diese Ideen einfach zu erklären, wenn wir uns als Programmierer folgende Dinge vor Augen führen:

\n
    \n
  • Unser Code wird auf Situationen treffen, die wir nicht vorhergesehen haben. Falsche Werte, zu viele Werte, zu wenig Werte, abbrechende Verbindungen, Fehlbenutzung… als dies sind Dinge, die wir abfangen müssen. Als Programmierer können wir uns nicht auf schlechte Umstände berufen, sondern müssen wie ein Anschnallgurt in einem Auto oder die Displayfolie auf einem Smartphone davon ausgehen, das früher oder später etwas Schlimmes passieren kann.
  • \n
  • Unsere Programmierung kann nicht alle Fehlerfälle vorhersehen und abfangen. Deswegen muss sie auch nachträglich verbessert werden können – durch uns, oder einen anderen Programmierer.
  • \n
  • Wir arbeiten fast immer in Teams. Unser Code wird von Programmierern gewartet oder verändert werden müssen, die nicht das selbe Wissen wie wir haben – oder nicht die selben Tools. Ironischerweise können wir selber dieser Programmierer sein: Schmökert doch mal in Programmierung, die ihr vor zwei Jahren produziert habt, und versucht die für die Entwicklung notwendigen Tools aufzusetzen.
  • \n
\n

Deshalb muss für mich persönlich Programmierung die folgende Attribute besitzen – die den Attributen der Kalaschnikow-Gewehr-Familie ähneln:

\n
    \n
  1. Sie muss eine Aufgabe präzise und möglichst wenig komplex lösen. Nicht weniger… aber auch nicht mehr.
  2. \n
  3. Sie muss tolerant mit Fehlern umgehen – und sinnigerweise im Falle eines Fehlers dem Benutzer oder zumindest einem Programmierer mitteilen können, was der Fehler war.
  4. \n
  5. Sie muss verständlich sein, damit sie auch von anderen Programmierern mit anderem technischen Verständnis oder Tools über einen längeren Zeitraum hinaus gewartet werden kann.
  6. \n
\n

Diese drei Kernideen, einmal verinnerlicht, erlauben eine ganze Menge Entscheidungen zu fällen, ohne jeden Handgriff vorher explizit durchdenken bzw. erforschen zu müssen. Deswegen nenne ich diese Philosophie Kalaschnikow-Programmierung.

\n

Einfachheit und Verständlichkeit in der Programmierung…

\n

Die Genialität einer Konstruktion liegt in ihrer Einfachheit. Kompliziert bauen kann jeder.

\n
Sergej P. Koroljow
\n

Als Programmierer lieben wir Abkürzungen, die uns schneller und produktiver machen. Und außerdem mögen wir es auch, unsere Genialität und Professionalität in unserer Arbeit zu beweisen. Das Problem entsteht, wenn wir unsere Arbeit von zu vielen, zu komplexen Ideen abhängig machen.

\n

In diesem Sinne sollten wir nicht nur bei der Benennung von Komponenten daran denken, dass auch andere Programmierer diese verstehen können müssen. Auch übermäßig komplexe Sprach-Konstrukte oder wenig bekannte Mechanismen bzw. Funktionen müssen vermieden werden. Viele Programmiersprachen bieten für einige Problem wenig bekannte, sehr elegante Lösungen an – die aber leider dann kaum jemand versteht. Das Wort „obskur“ sollte niemals den Zustand von Programmierung beschreiben dürfen.

\n

Wenn also eine solche Lösung verwendet wird, sollte diese zumindest dokumentiert werden – besser ist es aber gegebenenfalls, diese Lösung durch ein wenig komplexes, dafür gerne auch längeres Stück Programmierung zu ersetzen.

\n

Wer keine Dokumentation für seine Programmierung hinterlässt, möchte in seinem Urlaub telefonisch Fragen dazu beantworten.

\n

Vor geraumer Zeit war ich noch der Meinung, dass Programmierung ohne Inline-Dokumentation eine schlechte Angewohnheit ist. Inzwischen glaube ich das Gegenteil: Wenn eine Programmierung einer Erklärung für den nächsten Programmierer bedarf, ist sie möglicherweise einfach nur zu komplex und sollte unbedingt umgeschrieben werden.

\n

Wir neigen dazu, Over-Engineering zu betreiben, wenn wir uns hinlänglich bekannte Probleme lösen. Um mögliche zukünftige Probleme vorweg zu nehmen, erzeugen wir oft erheblichen Aufwand – nur um später festzustellen, dass wir einerseits dieses angenommene Problem nie hatten, und andererseits die von uns entwickelte Lösung niemals weiter verwendet worden war. Hier sollte Pragmatismus und Augenmaß unser Leitbild sein.

\n

Nicht zuletzt müssen wir uns bei überbordender Komplexität fragen, warum wir diesen Aufwand betreiben. Gemäß dem Pareto-Prinzip kann eine einfache, stabile Lösung kurz- wie auch langfristig weniger Aufwand verursachen – und ist damit in der Herstellung nicht nur einfacher, sondern auch günstiger.

\n

…und Einfachheit bei den Werkzeugen

\n

Alles sollte so einfach wie möglich gemacht werden, aber nicht einfacher.

\n
Albert Einstein
\n

Vor geraumer Zeit benötigte ein Webentwickler nur einen Webserver mit einem Scriptinterpreter, einen Datenbankserver, und einen Code-Editor. Diese Zeiten sind längst passé, und wir erweitern die notwendige Technologie immer wieder um neue Komponenten, die unsere Arbeit angenehmer und schneller macht – solange diese Technologie funktioniert. So kann es heute notwendig sein, für die Entwicklungsarbeit an einem Web-Projekt Docker und die passenden Docker-Images, Gulp oder Grunt, dazu NodeJS, vielleicht noch SASS, Git, lokale Linter (um nicht mit dem automatischen Linter im Continuous Integration zu kollidieren), Editorconfig für den richtigen Code-Style und Unmengen an weiterer Tools zu benötigen.

\n

Wir, die wir diese Tools in jahrelanger Arbeit zusammengetragen, eingerichtet und später dann auch verstanden zu haben, haben damit für andere Entwickler in unserem Projekt eine Einstiegshürde geschaffen. Schlimmer noch können wir Teile in unsere Toolchain eingeführt haben, die ein Verfallsdatum haben und nach einer gewissen Zeit ein Projekt nur noch schwer wartbar machen. Und nicht zuletzt kann ein Defekt in einem Teil unserer Toolchain die sorgsam aufeinander gestapelten Abhängigkeiten zum Einsturz bringen. Wenn z.B. NodeJS nicht funktioniert, funktioniert Gulp nicht, funktioniert unser SASS-Compiler nicht, gibt es kein CSS.

\n

Wenn wir schon für unseren Entwicklungsprozess eine komplexe Toolchain erfordern, müssen wir (z.B. in der README.md des Projekts) die grundsätzlichen Anforderung und die Installation dieser Tools erläutern. Im Idealfall liefern wir ein Installations-Skript mit, dass alle notwendigen Abhängigkeiten und Tools installiert. Wenn man ein paar mal solche Anleitungen oder Skripte geschrieben hat, wird man vielleicht verstehen, welches Erbe man zukünftigen Programmierern hinterlassen hat.

\n

Welches Problem löst das – und welches Problem verursacht das?

\n

Weitere Ideen und Inspiration zu diesem Thema finden sich in dem Artikel „Zurück zur Werkbank“.

\n

Unser Verhältnis zu Code… und anderen Menschen

\n

Gerade wenn wir an großen Projekten oder in einem Team arbeiten, sind wir als Programmierer einerseits gefordert, unsere fundierte Meinung einzubringen. Andererseits ist aber für das Gelingen unserer Programmierung notwendig, dass alle Beteiligten das selbe Verständnis für die Anforderung an die Umsetzung eines solchen Projektes haben.

\n

Als Programmierer können und sollen wir eine eigene Meinung zu Tools, Coding-Styles, Dokumentations-Standards oder Testing haben. Diese persönliche Meinung wird aber in Teams bzw. in Projekten immer nachrangig zu beschlossenen Standards sein. Wenn wir persönlich der Meinung sind, dass Tabs besser geeignet sind zum Einrücken, im Projekt bisher aber immer vier Leerzeichen verwendet wurden, müssen wir aus Gründen der Konsistenz und Erwartbarkeit von Code uns an diese Vorgaben halten. Das macht unsere eigene Meinung nicht schlecht oder überflüssig. Aber eine Abweichung von der Norm ist in einem komplexen Umfeld immer eine Quelle für Fehler. Das Prinzip des Clean Code ist hier eine äußerst gute Grundlage.

\n

Wenn uns geschlossene Beschlüsse nicht gefallen, sollten wir auf eine Änderung hinwirken. Wenn eine Änderung beschlossen wurde, müssen wir diese aber auch konsequent und ggf. rückwirkend umsetzen. Mitten in einem Projekt einzelne Teile in einem anderen Zeichensatz oder mit einem neuen Datenbank-Adapter zu betreiben löst an der Stelle, an der wir den Code verwenden, vielleicht einiges an Problemen – aber der nächste Programmierer steht auf einmal vor zwei verschiedenen Wegen, wie ein Problem gelöst werden kann. Dies ist eine Quelle für Missverständnisse und Fehler, sowie für langsam vor sich hinrottenden Code.

\n

Einsame Entscheidungen können wir nur für Projekte treffen, bei denen nur wir betroffen sind. Sobald Kunden, Nutzer oder andere Programmierer involviert sind, müssen wir uns auch über ihre Bedürfnisse klar werden.

\n

Fehlertoleranz

\n

Wir sollten niemals erwarten, dass eine Variable den Wert enthält, den wir annehmen. Bei einer Nutzereingabe ist dies offensichtlich, bei einer Abfrage aus einer Datenbank oder einer Schnittstelle fällt uns diese Annahme schon deutlich schwerer.

\n

Dabei geht es gar nicht so sehr um korrektes Quoting – wobei inkorrektes Quoting nicht nur Fehler, sondern gegebenenfalls auch Sicherheitsprobleme auslösen kann. Viel mehr geht es darum, Fehlerbehandlung als einen Weg zu begreifen, der auch die Performance steigert. Die Prüfung auf das Vorhandensein eines Werts kann es erlauben, frühzeitig aus einem komplexen Programmteil zurückzukehren, ohne mit falschen Grundannahmen eine aufwändige Berechnung durchzuführen. Auch im Bereich des Templatings kann die Prüfung auf eine leere Variable die Ausgabe ganzer Template-Teile überflüssig machen.

\n

Unsere Programmierung kann auch auf andere Art und Weise fehlertoleranter werden. In fast jeder Sprache erlauben Funktionen eine Bereinigung von Variablen, zum Beispiel in dem umschließende Leerzeichen mittels trim entfernt werden, oder ein String per Casting sicher in eine Zahl verwandelt werden kann. Die einfache Konvertierung in erwartbare Werte kostet uns als Programmierer und unser Programm meist deutlich weniger Aufwand als den Nutzer die Korrektur einer fehlerhaften Eingabe abnötigen würde – und erspart möglicherweise die Programmierung eines Prozesses, der den Nutzer zur Fehlerkorrektur auffordert.

\n

Nicht zuletzt können wir mittels Mustererkennung bzw. -ersetzung Fehler erkennen und vielleicht sogar unbemerkt beheben. Wenn der Nutzer bei der Eingabe einer Telefonnummer oder Kreditkartennummer keine anderen Zeichen als Ziffern eingeben soll, können wir als Programmierer diese Nicht-Ziffern-Zeichen doch problemlos entfernen – und müssen diese stupide Aufgabe nicht unseren Nutzern auferlegen.

\n

Wenn wir eine Fehlermeldung oder Exception-Message schreiben, muss diese Fehlermeldung außerhalb des lokalen Kontextes Sinn ergeben. Die Fehlermeldung „Ein Fehler trat auf, wenden Sie sich bitte an Ihren Administrator“ ist besonders dann frustrierend, wenn man selber der Administrator ist. Benutzen wir doch die von jedem Programmierer einforderbare Fähigkeit Dinge zu beschreiben auch für Fehlermeldungen, und verwandeln ein „Connection fail“ in ein „Der HTTP-Aufruf https://www.example.com scheitert mit Status-Code 503.“. Niemand beschwert sich über zu aussagekräftige Fehlermeldungen.

\n

Weitere Ideen und Inspiration zu diesem Thema finden sich in dem Artikel „Die Verantwortung von Software“.

\n

Fazit

\n

Die Menge an praktischen Handlungsempfehlungen aus den drei Kernideen der Kalaschnikow-Programmierung sind deutlich vielfältiger, als dieser Artikel auch nur im Ansatz beleuchten kann. Dementsprechend sind die hier aufgeführten Anekdoten eher Beispiele – aber nur die Spitze des Eisbergs. Vielmehr ist bei der Kalaschnikow-Programmierung das Grundverständnis bzw. die Grundhaltung die entscheidende Konstante, von denen ausgehend Entscheidungen getroffen werden können.

\n

Ich persönlich schaffe es auch nicht immer, mich an diese Idee zu halten. Aber sie führt mich immer wieder zurück, und hilft mir bei der Entscheidungen zwischen ansonsten technisch gleichwertig korrekten Lösungen.

\"\"", - "summary": "Dies ist meine Philosophie über Programmierung. Es gibt viele davon, aber diese hier ist meine.", - "date_published": "2019-10-14T18:58:23+02:00", - "date_modified": "2019-12-12T15:55:06+01:00", - "author": { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - }, - "authors": [ - { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - } - ], - "banner_image": "https://cdn.3960.org/favicon-192x192.png", - "language": "de-DE", - "image": "https://cdn.3960.org/favicon-192x192.png", - "tags": [ - "Programmierung", - "Technologie", - "Webdevelop", - "Meinung" - ] - }, - { - "id": "user/posts/2019-10-12-microsoft-flight-simulator-2020-was-er-bewirken-wird/index.md", - "url": "https://journal.3960.org/posts/2019-10-12-microsoft-flight-simulator-2020-was-er-bewirken-wird/", - "title": "Der Microsoft Flight Simulator 2020 – und was er bewirken wird", - "content_html": "

Microsoft hat vor Kurzem den neuesten Teil ihrer weltberühmten Flight Simulator-Serie angekündigt. Innerhalb kürzester Zeit war der Microsoft Flight Simulator 2020 in aller Munde – nicht zuletzt wegen seines spektakulären Trailers:

\n
\n

Nachdem sich der Staub etwas gelegt hat und immer mehr Informationen über dieses (augenscheinlich seit fünf Jahren im Geheimen produzierte) Projekt an die Öffentlichkeit dringen, gibt es eine erste Ahnung, was MSFS2020 für das Hobby der Flug-Simulation bedeuten kann.

\n\n

Was bisher geschah

\n

Der aus dem Jahre 2006 stammende letzte Simulator aus dem Hause Microsoft war der Microsoft Flight Simulator X – ein in seiner Zeit bahnbrechendes Stück Software, dass für lange Zeit den Maßstab für private Flug-Simulation gesetzt hatte.

\n

Die Jahre gingen ins Land, und der FSX verstaubte mehr und mehr: Die Grafik, das Flugmodell, alleine der Nachschub an Patches – all dies machte FSX trotz einer treuen Fangemeinde langsam überholt. Alle warteten auf einen Nachfolger.

\n

2012 erschien das Flugspiel Microsoft Flight. Die Annahme, dass es eine Fortsetzung des FSX sein würde, wurden enttäuscht. Stattdessen handelte es sich um ein vereinfachtes Spiel, dass Simulationsfreunde nicht zufrieden stellen konnte.

\n

Als weitere Signale schloss Microsoft 2009 das für den FSX verantwortliche Studio, und lizensierte die Engine an andere Unternehmen, die mehr oder weniger würdige Nachfolger für den Flight Simulator produzierten. Keines dieser Produkte konnte aber bahnbrechende Erfolge feiern.

\n

Simulationsfreunden wurde klar, dass von Microsoft nichts mehr zu erwarten sein würde. So zog die Herde weiter, unter anderem zu X-Plane 11, oder schwelgte mit Prepar3d in Nostalgie.

\n

Götterdämmerung

\n

Das alles änderte sich schlagartig mit den ersten Trailern für den Microsoft Flight Simulator 2020, die für alle überraschend nicht nur einen bereits sehr fertig wirkenden Simulator zeigte.

\n

Die Trailer zeigten ein Flugerlebnis, das vor allen Dingen außerhalb der Simulations-Gemeinde Interesse an MSFS2020 weckte. In persönlichen Gesprächen mit Gamern zeigte sich, dass Microsoft einen Nerv getroffen hatte: Auch Spieler ohne Vorerfahrung mit Flugsimulatoren interessierten sich auf einmal brennend für den MSFS2020. Die ansprechende Grafik und die einsteigerfreundliche Präsentation machten Lust auf mehr.

\n

Und die alten Simulationshasen? Immer, wenn der Hype-Train durch den Ort brauste, saßen sie erstmal gemütlich auf der Veranda und beobachteten das Spektakel skeptisch vom Rand aus. So auch diesmal: Viele hatten Microsoft ihre vorherigen Fehler in Bezug auf Flugsimulatoren nie verziehen.

\n

Direkt auf diese Überholspur

\n

Microsoft war von vorne herein bewusst, dass ihr Verhalten der Vergangenheit viel Porzellan zerschlagen und bei Simulations-Enthusiasten Skepsis und Misstrauen hinterlassen hatte. Schon früh hatte Microsoft deswegen den Austausch mit der Community gestartet, und ein offenes Ohr für Ideen und Wünsche gezeigt. Doch alleine damit ließen sich nicht alle Zweifel zerstreuen – Fakten mussten her.

\n

In einem PR-Meisterstreich lud Microsoft darauf hin ausgesuchte Simfluencer, Flug-Blogger sowie Fachpresse zu einem Event ein, bei der sie nicht nur das frühe Produkt selber ausprobieren konnten, sondern auch mit den Entwicklern des Microsoft Flight Simulator 2020 direkt ins Gespräch kommen konnten.

\n
\n

Ob es in dem Youtube-Video von FlightChops, dem Youtube-Video von Frooglesim oder dem Artikel auf Helisimmer.com ist: Alle Simfluencer konnten nicht verbergen, dass Microsoft ihre Skepsis in Begeisterung und Vorfreude verwandelt hatte.

\n

Die Situation heute

\n

Da die ersten Erfahrungen mit dem MSFS2020 vorliegen, verdichtet sich ein Bild.

\n
    \n
  • Der Microsoft Flight Simulator 2020 hat sowohl Neueinsteiger als auch Veteranen als Zielgruppe.
  • \n
  • Grafisch wird der Simulator alles bisher dagewesene in den Schatten stellen – zumal der Simulator für die gesamte Welt hochauflösendes Material mit Vegetation und Bebauung anbieten wird.
  • \n
  • Das Flugmodell wird ebenfalls seinesgleichen suchen. Sowohl in Berichten als auch in Videos ist zu sehen, wie diffizil Fluggeräte reagieren.
  • \n
  • Auch beim Wettermodell ist eine Detailtiefe zu sehen, die bisherige Simulatoren alt aussehen lässt.
  • \n
  • Inwiefern Online-Spiel, Air Traffic Control und andere Prozeduren von vorne herein integriert sind, ist offen. Es ist aber erkennbar, dass auch hier der MSFS2020 nicht kleckern, sondern klotzen wird.
  • \n
  • Über den Bereich Performance (gerade für Virtual Reality) ist noch nicht viel bekannt – hier besteht schon der Verdacht, dass für bahnbrechende Bilder bahnbrechende Hardware benötigt wird. Für Simulationsfreunde ist aber die Verwendung von High-End-Hardware kein Novum.
  • \n
  • Es steht zu erwarten, dass die Palette an Fluggeräten schon nach kurzer Zeit sich rapide füllen wird, da etliche Drittanbieter bereits mit der Produktion neuer Fluggeräte für den MSFS2020 begonnen haben.
  • \n
  • Nicht zuletzt wird aufgrund der anvisierten Zielgruppe wahrscheinlich das bisher im Simulatorgewerbe übliche Zusammenbasteln des Simulators ein Ende haben. Während heutige Simulatoren aus einzelnen Softwarekomponenten zusammengesetzt werden müssen, die auf unterschiedlichen Bezugswegen und mit beliebig komplexen Installationsmechanismen daher kommen, wird Microsoft wahrscheinlich bei Erwerb wie Installation einen integrierten Katalog anbieten, ähnlich wie Steam oder Origin.
  • \n
\n

Alles in allem scheint der Microsoft Flight Simulator 2020 aller Voraussicht nach einen neuen Standard für den Bereich der Heim-Flugsimulatoren setzen zu können.

\n

Wie geht's weiter mit den anderen Flugsimulatoren?

\n

Der MSFS2020 bedeutet für etablierten Flugsimulatoren eine ernsthafte Konkurrenz. Gerade die Simulatoren, die ebenfalls die hochgradig realistische Zivil-Fliegerei abbilden, müssen sich sehr warm anziehen:

\n
    \n
  • Für X-Plane 11 wird in den ersten Jahren das große Angebot von Fluggeräten gegenüber MSFS2020 noch einen Vorteil darstellen. Aber sowohl in der Darstellung der Szenerie als auch beim Wettermodell scheint der Neuankömmling dem guten alten X-Plane 11 überlegen zu sein. In Sachen Performance scheinen beide Simulatoren gleich hungrig zu sein – der MSFS2020 scheint aber mit der Hardware ein deutlich beeindruckenderes Erlebnis zu zaubern.
  • \n
  • Noch schlimmer sieht es für Prepar3d aus: Die angestaubte Engine wird augenscheinlich gegen den MSFS2020 keine Chance haben. Einzig die breite Palette an Fluggeräten kann anfangs noch helfen – aber alleine bei dem Angebot an Szenerien wie auch bei dem Flugmodell wird die Luft sehr schnell sehr dünn.
  • \n
\n

Eine Chance haben dagegen Simulations-Projekte, die leicht außerhalb des Fokus liegen – wenn sie ihre Karten gut spielen:

\n
    \n
  • Der Digital Combat Simulator muss sich keine Sorgen machen: Obwohl sein Angebot an Szenerien geradezu winzig ist, ist die Konzentration auf Kampfflugzeuge bzw. -hubschrauber ein Feld, dass augenscheinlich nicht von MSFS2020 besetzt werden wird.
  • \n
  • Der Aerofly FS2 ist im VR-Bereich bzw. auf kleiner Hardware grafisch ungeschlagen. Zwar verfügt der Simulator bis heute über kein realistisches Wetter, globale Szenerie-Abdeckung, Air Traffic Control, eine lebende Umwelt oder Multi-Player. Aber durch die Konzentration auf das Flugmodell, Performance und Einsteigerfreundlichkeit könnte Aerofly FS2 auch mittelfristig seine Bedeutung im Heim-Simulations-Markt behalten.
  • \n
  • Das bis heute nicht veröffentlichte Deadstick von REMEX Software dagegen könnte die Konzentration auf eine lebende Umwelt und ein Meta-Spiel die Zukunft sichern. Gerade dass Meta-Spiel, das der Fliegerei einen Sinn gibt, und das kleine, heimatgefühlinduzierende Fluggebiet könnte eine langfristige Fan-Gemeinde aufbauen. Wenn Deadstick denn jemals erscheinen sollte.
  • \n
\n

Für alle Simulatoren kann ein Weg sein, die Power des Internets zu nutzen:

\n
    \n
  • Die Kooperation mit einem Flug-/Satelliten-Bild-Anbieter, zusammen mit der Verwendung von Open Street Maps und einer Technologie zur Erzeugung von dreidimensionaler Vegetation und Bebauung, kann global die Abdeckung wie auch die Präsentation stark verbessern.
  • \n
  • Scheinbar unbelebte Umwelt können über Schnittstellen mit echten Wetterdaten, echten Flugverkehrsdaten und z.B. auch echten Schiffsbewegungen dynamischer erscheinen. Fahrzeuge dagegen können auf den durch Open Street Maps bekannten Straßen animiert werden.
  • \n
  • Simulatoren müssen selber offene, gut dokumentierte, stabile Schnittstellen anbieten, die dritten Parteien (z.B. auch Community-Entwicklern) die Erweiterung des Simulators erlauben.
  • \n
  • Darüber hinaus muss Nutzerfreundlichkeit mehr in den Fokus rücken; nicht nur innerhalb des eigentlichen Simulators, sondern auch bei der Installation und Aktualisierung von zusätzlichen Add-Ons und Plugins.
  • \n
\n

Wie die einzelnen Simulatoren sich entwickeln hängt aber neben dem Produkt und dem Verhalten des Herstellers nicht zuletzt von der jeweiligen Community ab – ein schwer einzuschätzender Faktor. Schon bei FSX konnte man beobachten, dass ein technisch und funktional schon lange abgehängtes Produkt immer noch erfolgreich sein kann.

\n

Fazit

\n

Alles in allem wird 2020 ein spannendes Jahr für die Flugsimulationsgemeinde – sei es für die Hersteller, sei es für die Schreibtisch-Flieger. Auf jeden Fall wird der MSFS2020 neue Impulse setzen, und das Hobby der virtuellen Fliegerei schlagartig einem vollkommen neuen Kreis von Nutzern eröffnen.

\n

Auch Simulationsfreunde, die nicht auf MSFS2020 setzen werden, werden seine Auswirkungen früher oder später auch in ihrem Lieblings-Simulator erleben.

\"\"", - "summary": "Microsoft hat vor Kurzem den neuesten Teil ihrer weltberühmten Flight Simulator-Serie angekündigt. Innerhalb kürzester Zeit war der Microsoft Flight Simulator…", - "date_published": "2019-10-12T18:20:22+02:00", - "date_modified": "2020-02-07T14:31:15+01:00", - "author": { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - }, - "authors": [ - { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - } - ], - "banner_image": "https://img.youtube.com/vi/ReDDgFfWlS4/hqdefault.jpg", - "language": "de-DE", - "image": "https://img.youtube.com/vi/ReDDgFfWlS4/hqdefault.jpg", - "tags": [ - "MSFS2020", - "Fliegerei", - "Für Tumblr", - "Meinung", - "Simulation", - "Spiel", - "The Cool", - "Aerofly FS2", - "X-Plane", - "DCS" - ] - }, - { - "id": "user/posts/2019-10-01-redirects-fuer-apache-verstehen/index.md", - "url": "https://journal.3960.org/posts/2019-10-01-redirects-fuer-apache-verstehen/", - "title": "Redirects für den Apache verstehen", - "content_html": "

Nach größeren Änderungen an einem Internetauftritt gibt es oft den Wunsch, veraltete URLs auf neue URLs umzuleiten. Dazu kann man in Webservern sogenannte „Redirects“ anlegen – Umleitungen für URLs. Sowohl Besucher mit Lesezeichen wie auch Suchmaschinen werden so eure neuen Inhalte umgeleitet, auch wenn sie eine alte URL aufrufen.

\n

Für den Apache Webserver gibt es 2½ Wege, wie man Redirects konfigurieren kann: redirect, redirectmatch sowie RewriteRule. Tatsächlich ist die korrekte und fehlerfreie Konfiguration von Redirects aber eine trickreiche Sache – und ein genaues Studium der jeweiligen Anleitung notwendig, um Redirect Loops zu vermeiden.

\n\n

Die 2½ verschiedenen Redirect-Direktiven funktionieren relativ ähnlich: Sie leiten eine Anfrage-URL, die einem bestimmten Muster entspricht, auf eine Ziel-URL um. Dazu können sie der Anfrage einen HTTP-Statuscode mitgeben, der den ursprünglichen Aufrufer mitteilt, aus welchem Grund und wie lange die Umleitung existiert.

\n

Wo kann ich für den Apache-Webserver Redirects einrichten?

\n

Der Ort für die Konfiguration von Redirects ist entweder die Apache-Konfigurationsdatei, oder aber (wenn der Webserver dieses Feature eingeschaltet hat) die .htaccess-Konfigurationsdatei direkt im Hosting-Verzeichnis bzw. der Document Root des Webauftritts.

\n

Wenn kein Apache zum Einsatz kommt, funktioniert diese Methoden nicht – in der Regel verfügt aber jeder Webserver über eine zumindest ähnliche Methode, wie Redirects eingerichtet werden können.

\n

Wie sollte ich für den Apache Redirects einrichten?

\n

Bei der Einrichtung von Redirects sollte in der Konfigurationsdatei vermerkt werden, warum die Redirects eingerichtet wurden, oder wann sie gelöscht werden können. Nichts ist schlimmer, als in einem Wust von 6.500 Redirects nicht mehr durchzublicken und auch nicht zu wissen, ob nicht 6.000 Regeln schon lange hätten weggeworfen werden können. Hier bieten sich Kommentare an:

\n
# Basic redirects START\nRedirect permanent "/index" "/"\n# Basic redirects END\n\n# SEO redirects DELETE AFTER 10/2020 START\nRedirect permanent "/artikel" "/articles"\nRedirect permanent "/kontakt" "/contact"\n# SEO redirects DELETE AFTER 10/2020 END
\n

Gerade bei Redirects, die Suchmaschinen von einer alten, nicht mehr im Einsatz befindlichen URL auf eine neue URL umleiten sollen, kann nach zwei Jahren spätestens davon ausgegangen werden, dass sie nicht mehr notwendig sind.

\n

Ein weiterer Vorschlag ist, die Direktiven für Redirects in Blöcke einzupacken, die testen, ob das für die Direktiven notwendige Modul überhaupt installiert ist:

\n
<IfModule mod_alias.c>\n  Redirect permanent "/index" "/"\n</IfModule>\n\n<IfModule mod_rewrite.c>\n  RewriteRule "^/source$" "/target" [R=301,L]\n</IfModule>
\n

Dies verhindert, dass der Webserver seinen Betrieb einstellt, falls das Modul deaktiviert wird. Andersherum kann man diese Blöcke natürlich weglassen, wenn man explizit möchte, dass das Deaktivieren von Modulen und der damit verbundenen Redirects auffällt. 😉

\n

Fallstricke bei der Konfiguration von Redirects im Apache

\n

Wenn man sich die Anleitung für mod_alias inklusive redirect und redirectmatch sowie die Anleitung für mod_rewrite inklusive RewriteRule genau anschaut, wird man überraschenderweise darauf stoßen, dass alle Varianten bei der Anfrage-URL nicht eine fixe URL entgegen nehmen, sondern ein Muster (bzw. Pattern). Diese Muster erwischen meistens mehr Anfrage-URLs, als man auf den ersten Blick vermuten möchte.

\n

Selbst die harmlos wirkende Redirect-Direktive sucht nach einem Muster (und nicht nach einem festen Wert), und hängt alle überschüssigen URL-Teile inklusive GET-Parameter an die Ziel-URL an:

\n
Redirect permanent "/source" "/target"\n# Redirects /source    to /target\n# Redirects /source123 to /target123\n# Redirects /source/12 to /target/12\n# Redirects /source/?a to /target/?a
\n

Das stellt eine größere Quelle für Verwirrung dar, da ein unbedarfter Einrichter mit der obigen Direktive nicht eine einzige Anfrage-URL umleitet, sondern tatsächlich eine unüberschaubar große Menge. Zudem kann eine frühere Regel nachfolgende Regeln unbenutzbar machen, ohne dass dies auf den ersten Blick auffällt:

\n
Redirect permanent "/source"    "/target"\nRedirect permanent "/source/12" "/target/some-special-place"\n\n# Redirects /source    to /target\n# Redirects /source123 to /target123\n# Redirects /source/12 to /target/12 (!)\n# Redirects /source/?a to /target/?a
\n

Reguläre Ausdrücke werden die Welt retten

\n

Verständlicher ist dort die RedirectMatch-Direktive, die von vorne herein mitteilt, dass sie mittels eines regulären Ausdrucks die Anfrage-URL in eine Ziel-URL umformt. Bei einer Fehlbedienung sind die Konsequenzen aber noch viel weitreichender:

\n
RedirectMatch permanent "/source" "/target"\n# Redirects /source               to /target\n# Redirects /source123            to /target\n# Redirects /source/12            to /target\n# Redirects /source/?a            to /target\n# Redirects /your-shiny/source/?a to /target
\n

Eine Entsprechung zu RedirectMatch findet sich bei mod_rewrite, das auch kompliziertere Umleitungen abbilden kann.

\n

mod_alias is designed to handle simple URL manipulation tasks. For more complicated tasks such as manipulating the query string, use the tools provided by mod_rewrite.

\n
mod_alias – Apache HTTP Server Version 2.4
\n

Die RewriteRule-Direktive hat entsprechende Fallstricke im Angebot, da sie ebenfalls mit regulären Ausdrücken arbeitet und je nach Einsatzort ein anderes Matching verwendet:

\n

In VirtualHost context, The Pattern will initially be matched against the part of the URL after the hostname and port, and before the query string (e.g. „/app1/index.html“). This is the (%-decoded) URL-path.
In per-directory context (Directory and .htaccess), the Pattern is matched against only a partial path, for example a request of „/app1/index.html“ may result in comparison against „app1/index.html“ or „index.html“ depending on where the RewriteRule is defined.
[…]
The removed prefix always ends with a slash, meaning the matching occurs against a string which never has a leading slash. Therefore, a Pattern with ^/ never matches in per-directory context.

\n
mod_rewrite – Apache HTTP Server Version 2.4
\n

Die Einschränkung für .htaccess-Dateien kann übrigens mit einem vorangestellten RewriteCond %{REQUEST_URI} aufgehoben werden, so dass sich die Regeln wieder analog zu ihrem Aufruf innerhalb einer <VirtualHost>-Direktive verhalten.

\n

Damit sehen die Matches wie folgt aus:

\n
RewriteRule "/source" "/target" [R=301,L]\n# Redirects /source               to /target\n# Redirects /source123            to /target\n# Redirects /source/12            to /target\n# Redirects /source/?a            to /target\n# Redirects /your-shiny/source/?a to /target
\n

Fallstricke zum Selberknüpfen

\n

Die Gefährlichkeit von Redirect wird dann offenbar, wenn man seine Funktionalität mit RedirectMatch und RewriteRule nachbaut. Denn tatsächlich benutzt Redirect implizit ein nicht wenig komplexes Pattern-Matching. Folgendes Beispiel zeigt, wie inhaltlich identische Regeln teilweise sehr unschuldig aussehen können:

\n
Redirect      permanent "/source"      "/target"\nRedirectMatch permanent "^/source(.*)" "/target$1"\nRewriteRule             "^/source(.*)" "/target$1" [R=301,L]
\n

Wie richte ich bombensichere Redirects für den Apache ein?

\n

Um sich komplett sicher zu sein, von wo nach wo Redirects erzeugt werden, sollten nur die Redirect-Direktiven mit regulären Ausdrücken verwendet werden. Diese weisen von vorne herein darauf hin, dass ihre Konfiguration wohl überlegt sein möchte. Dabei helfen die Steuerzeichen für PCREs bzw. Perl-kompatible reguläre Ausdrücke:

\n
    \n
  • ^: Stimmt mit dem Anfang einer Zeichenkette überein.
  • \n
  • $: Stimmt mit dem Ende einer Zeichenkette überein.
  • \n
  • ?: Das Zeichen bzw. die Gruppe vor dem ? ist optional.
  • \n
\n

Ein super-einfacher, direkter Redirect von exakt einer Anfrage-URL auf exakt eine Ziel-URL kann mittels Umschließung mit ^…$ realisiert werden:

\n
RewriteEngine On\nRewriteCond %{REQUEST_URI}\nRewriteRule "^/source$" "/target" [R=301,L]\n# Redirects /source to /target
\n

Wenn Anfrage-URLs ohne und mit abschließenden / erlaubt sein sollen, kann dieses Muster um ein optionales / erweitert werden:

\n
RewriteEngine On\nRewriteCond %{REQUEST_URI}\nRewriteRule "^/source(/)?$" "/target$1" [R=301,L]\n# Redirects /source  to /target\n# Redirects /source/ to /target/
\n

Wenn ganze URL-Bereiche auf eine Ziel-URL umgeleitet werden sollen, kann der letzte Teil der URL explizit mit einem Muster erfasst werden. .* erfasst bei regulären Ausdrücken eine beliebige Anzahl von beliebigen Zeichen:

\n
RewriteEngine On\nRewriteCond %{REQUEST_URI}\nRewriteRule "^/source/.*$" "/target/" [R=301,L]\n# Redirects /source/        to /target/\n# Redirects /source/example to /target/\n# Redirects /source/?abc    to /target/
\n

Und nicht zuletzt kann ein ganzer Bereich auch 1:1 auf einen anderen Bereich umgeleitet werden, indem man die überschüssigen URL-Teile der Anfrage-URL an die Ziel-URL weiterleitet:

\n
RewriteEngine On\nRewriteCond %{REQUEST_URI}\nRewriteRule "^/source/(.*)$" "/target/$1" [R=301,L]\n# Redirects /source/        to /target/\n# Redirects /source/example to /target/example\n# Redirects /source/?abc    to /target/?abc
\n

Und für nginx?

\n

Der nginx-Webserver erfreut sich stetig wachsender Beliebtheit, und verfügt über ähnliche Methoden namens rewrite zum Erzeugen von Redirects. Entsprechend sieht das letzte Beispiel für den Apache im nginx wie folgt aus:

\n
rewrite ^/source/(.*)$ /target/$1 permanent\n# Redirects /source/        to /target/\n# Redirects /source/example to /target/example\n# Redirects /source/?abc    to /target/?abc
\n

Eine besonders geniale Lösung für nginx-Redirects auf Stackoverflow schmeißt aber die gesamte Denkarbeit über Bord, und erlaubt tatsächlich das Anlegen einer einfachen Tabelle von Anfrage- und Ziel-URLs.

\n

Fazit

\n

Die Einrichtung von Redirects im Apache-Webserver kann schnell unbeabsichtigte Folgen heraufbeschwören, und sogar den gefürchteten Redirect-Loop mit seiner bekannten Fehlermeldung „Too many redirects“ provozieren. Die Einrichtung mit Augenmaß und das Studium der Anleitung bewahrt euch davor, euren Internetauftritt unerreichbar zu machen.

\n

Falls ihr Wünsche für Redirects in Form einer Liste „Anfrage → Ziel“ erhaltet, dürft ihr diese Liste nicht 1:1 in eure Redirect-Direktiven umwandeln, sondern solltet mit den obigen Beispielen eine präzisere Übersetzung durchführen.

\"\"", - "summary": "Nach größeren Änderungen an einem Internetauftritt gibt es oft den Wunsch, veraltete URLs auf neue URLs umzuleiten. Dazu kann man in Webservern sogenannte …", - "date_published": "2019-10-01T18:03:10+02:00", - "date_modified": "2020-01-06T12:37:22+01:00", - "author": { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - }, - "authors": [ - { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - } - ], - "banner_image": "https://cdn.3960.org/favicon-192x192.png", - "language": "de-DE", - "image": "https://cdn.3960.org/favicon-192x192.png", - "tags": [ - "Programmierung", - "Webdevelop", - "Apache" - ] - }, - { - "id": "user/posts/2019-09-22-vfr-ohne-technischen-schnickschnack/index.md", - "url": "https://journal.3960.org/posts/2019-09-22-vfr-ohne-technischen-schnickschnack/", - "title": "VFR ohne technischen Schnickschnack", - "content_html": "

\"\"

\n

Mein aktueller Lieblings-Flugsimulator Aerofly FS2 überredet mich immer wieder zu neuen Experimenten. Nachdem ich IFR ohne GPS in Aerofly FS2 ausprobiert hatte, animierte mich die Geschichte der Leuchttürmen und großen Beton-Pfeilen für Flugzeuge aus der Gründerzeit der Fliegerei, mir eine neue Aufgabe vorzuknöpfen: Navigation ohne moderne technische Hilfsmittel an Bord!

\n\n

Die Herausforderung

\n

Die Aufgabe: Ohne jedes technische Hilfsmittel zur Funk- oder Satelliten-Navigation an Bord sicher und zuverlässig mit dem simulierten Flugzeug einen Cross-Country-Flug vom Start- zum Zielflughafen zu finden. Damit entfällt nicht nur das allgegenwärtige GPS, sondern auch die Nutzung von VOR- und NDB-Empfängern. Was erlaubt ist: eine gute Karte, ein Kompass, eine Uhr und ein guter Plan.

\n

Inspiration für solche Abenteuer finden sich z.B. in diesem Youtube-Video, in dem Flugnavigation ohne technische Hilfsmittel im echten Leben zu bewundern ist.

\n
\n

VFR nach diesen Regeln verändert deutlich die Art und Weise, wie ein Flug in einem Simulator durchgeführt werden muss:

\n
    \n
  1. Vor der Flugplanung muss deutlich genauer das Wetter überprüft werden. Nicht nur eine hinreichend hohe Sichtweite muss gegeben sein – auch der Wind spielt beim VFR eine wichtigere Rolle als bei der Fliegerei mit Navigationsunterstützung.
  2. \n
  3. Im Vorfeld muss ein präziser Flugplan ausgearbeitet werden, bei dem der jeweilige Kurs und die Distanzen vermerkt werden müssen, sowie die daraus resultierenden Zeiten.
  4. \n
  5. Der Flugplan muss dabei nicht zwangläufig der kürzeste Weg sein, sondern vor allen Dingen sich an aus der Luft sichtbaren Merkmalen orientieren.
  6. \n
  7. Im Flug wiederum wollen der Kurs, die Geschwindigkeit und die Zeit beachtet werden, um ein Gefühl dafür zu bekommen, wie weit man auf dem Flugplan bereits gekommen ist.
  8. \n
  9. Und nicht zuletzt muss der Pilot beständig aus dem Fenster schauen, um Geländemerkmale wieder zu erkennen, an Hand derer er oder sie seine Position feststellen kann.
  10. \n
\n

Statt also seine Hände in den Schoß und sein Schicksal in die Hände des Autopiloten zu legen, ist bei Gelände-VFR-Flügen Aufmerksamkeit und Konzentration gefragt.

\n

Disclaimer: Natürlich ist diese Anleitung weder vollständig, professionell, noch für die Verwendung im wahren Leben geeignet. Andererseits kann diese Anleitung auch für andere Simulatoren genutzt werden, wie zum Beispiel den Microsoft Flight Simulator oder X-Plane.

\n

Aeroflys Arbeitspferde für Gelände-VFR

\n

In Aerofly FS2 gibt es mehrere Fluggeräte, die heimliches Spicken auf etwaigen Navigationsgeräten unterbinden… weil sie nämlich über keine Navigationsgeräte verfügen. Neben den Flugzeugen aus dem Ersten und Zweiten Weltkrieg eignen sich vor allen Dingen die Doppeldecker im Bestand sehr für VFR-Fliegerei: Sowohl die Bückner Jungmeister als auch die Pitts S-2 verfügen über eine ausgezeichnete Aussicht – und keinen einzigen Funknavigations-Empfänger.

\n

Bei X-Plane eignet sich z.B. die mitgelieferte Aero-Works Aerolite 103 oder Stinson L-5 Sentinel für Flüge ohne Navigations-Bordinstrumente.

\n

\"TaxifahrerTaxifahrer brauchen kein GPS: Unterwegs mit der Pitts S-2

\n

Wetter: Wichtiger denn je

\n

Schon vor Beginn der eigentlichen Flugplanung muss ein scharfer Blick in die Wetterdaten geworfen werden. Die Mindestanforderungen sind natürlich die Visual Meteorological Conditions (VMC). Für VFR (MVFR) muss die Sichtweite mindestens 5SM (3SM) und die Wolkenuntergrenze mindestens 3.000ft (1.000ft) sein. Besseres Wetter ist natürlich zu bevorzugen, da das Gelände sichtbar sein muss.

\n

Zu beachten ist die Korrelation zwischen Wolken und Flughöhe: Da unsere Flughöhe mindestens 500ft über dem nächsten Hindernis in 2.000ft Umkreis sein muss (bzw. 1000ft über dem nächsten Hindernis in 2.000ft Umkreis in bebauten Gelände), kann nicht jedes Wetter unterflogen werden. Außerdem sollten wir eine Flughöhe von 3.000ft AGL nicht überschreiten, um das Gelände im Blick zu behalten. Nicht zuletzt sollte die Regel bedacht werden, dass je nach Kurs eine unterschiedliche Flughöhen eingehalten werden muss:

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Kurs 0–179°Kurs 180–359°
1.500ft MSL2.500ft MSL
3.500ft MSL4.500ft MSL
5.500ft MSL6.500ft MSL
7.500ft MSL8.500ft MSL
9.500ft MSL10.500ft MSL
11.500ft MSL12.500ft MSL
\n

All diese Bedingungen zusammen machen die Flugplanung nochmals herausfordernd.

\n

Nicht zuletzt muss die Windrichtung und -stärke vermerkt werden. Je nach eigener Flugrichtung und -geschwindigkeit wird Wind den eigenen Kurs verfälschen und sollte von vorne herein mit einkalkuliert werden.

\n

Flugpläne für Sichtflieger

\n

Grundlage für einen Cross-Country-VFR-Flug ist ein gut durchdachter Flugplan. Glücklicherweise hat Aerofly FS2 ein eingebautes Tool zur Erstellung einfacher Flugpläne.

\n

Für eine besondere Herausforderung sorgt die Tatsache, dass eher tief geflogen wird. Dementsprechend muss bei der Planung berücksichtigt werden, ob es Hindernisse oder Flugverbotszonen gibt, die den eigenen Flugplan beeinflussen.

\n

Flugpläne werden vom Start zum Ziel gedanklich in Streckenabschnitte zerlegt. Dabei ist es sinnvoll, Geländemerkmale zu finden, die den Verlauf und das Ende jedes einzelnen Streckenabschnitts markieren. Auch hierbei unterstützt Aerofly FS2, indem es automatisch Wegpunkte (und damit Streckenabschnitte) in den Flugplan einfügt.

\n

Für einen Flugplan geeignete Geländemerkmale müssen gut aus der Luft erkennbar sein. Wichtig ist dabei, sich wirklich große Merkmale auszusuchen. Ein vom Boden aus imposanter Hügel kann aus der Luft einfach nur wie ein flacher Hügel aussehen, oder ein malerisch von Bäumen eingefasster Fluss aus der Luft fast unsichtbar sein.

\n

Für den letzten Streckenabschnitt gibt es auf jeden Fall ein gut sichtbares Geländemerkmal: Den Zielflughafen.

\n

Die Verwendung von Geländemerkmalen

\n

Folgende Geländemerkmale eignen sich in der Regel sehr gut:

\n
    \n
  • Küstenlinien, z.B. von Meeren (unübersehbar), Seen und große Flüsse
  • \n
  • Markante Berge bzw. hohe Bergketten
  • \n
  • Größere Areale mit auffälliger Vegetation oder Bebauung
  • \n
  • Große Straßen (auf denen Nachts auch Verkehr ist)
  • \n
\n

Dabei unterscheidet man linienförmige von punktförmigen Geländemerkmalen. Linien werden z.B. durch Küstenlinien, Vegetationsgrenzen, Straßen oder Wasserwege erzeugt – alles, was länger als ein paar Kilometer ist. Punktförmige Geländemerkmale sind z.B. markante Bergspitzen, Seen oder Bauwerke.

\n

Ein gutes Hilfsmittel zur Identifikation von Geländemerkmalen sind Luft- oder Satellitenaufnahmen.

\n

\"AusflugAusflug zum Golf(en), immer der Straße nach

\n

Geländemerkmale könne auf vielfältige Weise für den Flugplan verwendet werden:

\n

1. Leitlinien

\n

Die einfachste Methode ist es, einem linienförmigen Geländemerkmal als Leitlinie zu verwenden, dem der Flugplan bzw. das Flugzeug folgen muss. Der Flugplan wird z.B. parallel zu einer großen Straße oder einer Bergkette gelegt, an der entlang geflogen wird. Damit entfällt die Aufgabe, den Kurs des Flugzeugs zu überwachen.

\n

In den meisten Fällen werden Geländemerkmale nicht direkt zum Zielflughafen führen. Wichtig ist es also für den Piloten, einen Anhaltspunkt für das Ende eines Streckenabschnitts zu haben, zum Beispiel ein weiteres Geländemerkmal.

\n

\"HighwaysHighways sind aus der Luft eine Augenweide

\n

2. Auffanglinien

\n

Eine sehr gute Methode zur Orientierung ist die Verwendung einer Auffanglinie. Dabei wird der aktuelle Streckenabschnitt von einem anderen, gut sichtbaren linienförmigen Geländemerkmal gekreuzt und ergibt somit ein deutlich sichtbares Signal, an welcher Stelle des Streckenabschnitts man sich befindet. Sinnigerweise ist eine Auffanglinie am Ende eines Streckenabschnitts vorhanden, so dass das Ende des Abschnitts unübersehbar wird.

\n

Das Prinzip der Auffanglinie ist vielseitig nutzbar. So können Streckenabschnitte ohne Leitlinie auskommen, solange das Ende des Abschnitts von einem quer zur Flugrichtung verlaufenden Geländemerkmal begrenzt wird. Dabei ist es dann auch nicht so wichtig, auf welchem Punkt das eigene Flugzeug genau auf die Auffanglinie trifft, solange man sich sicher ist, ob der Linie dann nach links oder rechts gefolgt werden muss. Hier hilft ggf. der Trick, weiter nach links zu zielen, wenn man an der Auffanglinie nach rechts fliegen muss bzw. umgekehrt.

\n

Ein ausgezeichnetes Beispiel für eine gut nutzbare Auffanglinie ist z.B. die Porta Westfalica, eine von einem Fluss durchbrochene Bergkette… oder der Overseas Highway in Florida, der von Norden kommend schwer zu verfehlen ist.

\n

\"AufAuf zu den Flamingos - die Küste fängt uns auf

\n

3. Orientierungspunkte

\n

Auf Streckenabschnitte können an Stelle von Auffanglinien auch Orientierungspunkte verwendet werden, um den Fortschritt entlang des Streckenabschnitts einschätzen zu können, oder um das Ende des aktuellen Streckenabschnitts zu markieren.

\n

Städte, Industriekomplexe, auffällige Brücken oder Flughäfen sind hervorragende Möglichkeiten, eindeutig seine Position festzulegen. Ein todsicheres Zeichen für das Erreichen des letzten Streckenabschnittes ist in der Regel ein Flughafen, der Tag wie Nacht gut zu sehen sein sollte.

\n

Im Gegensatz zu linienartigen Geländemerkmalen besteht bei punktförmigen Geländemerkmalen aber immer die Gefahr, diese zu verfehlen oder schlicht zu übersehen.

\n

4. Ohne Gelände: Zeitmessung via Stoppuhr

\n

Das größte Problem besteht bei Flugplänen über eintöniges Gelände ohne Leit- und Auffanglinien sowie ohne Orientierungspunkte – wenn zum Beispiel das Ziel eine einzelne Insel weit draußen im Meer ist. Da die Landschaft hier keinen Anhaltspunkt für Abweichungen vom Kurs darstellt, hilft hier nur die penible Messung von Kurs und Zeit. Die im Vorfeld berechneten Längen des Streckenabschnitts kann durch die Fluggeschwindigkeit geteilt werden, um die Flugdauer für einen Streckenabschnitt zu berechnen. Wichtig ist dabei, die Einheiten korrekt umzurechnen:

\n
    \n
  • 1 Nautische Meile = 1,852 km
  • \n
  • 1 Standardmeile = 1,609 km
  • \n
  • 1 Knoten = 1 Nautische Meile / h
  • \n
\n

Daraus resultiert: Zeit (h) = Strecke (NM) / Geschwindigkeit (kts)

\n

Zu Beginn des Streckenabschnitts wird eine Stoppuhr gestartet, so dass beim Erreichen der vorher kalkulierten Zeit der Pilot weiß, dass er das Ende des Streckenabschnitts erreicht hat. Dummerweise hat diese Methode den Nachteil, dass viele Faktoren die tatsächliche Flugzeit beeinflussen. Hier hilft nur die strikte Kontrolle des Kurses und der Geschwindigkeit – und ein waches Auge.

\n

Vom Winde verweht: Kurs & Geschwindigkeit

\n

Das eigentliche Problem beim Abfliegen eines Flugplans nach Kompass ohne weitere Geländemerkmale ist, dass Windrichtung und -geschwindigkeit die Position des Flugzeugs unbemerkt verändern können. Während Gegenwind die Geschwindigkeit über dem Boden herab- und Rückenwind die Geschwindigkeit über dem Boden heraufsetzt, kann Seitenwind das Flugzeug vom eigentlich geplanten Kurs abbringen.

\n

\"SchildkrötenSchildkröten lieben die Einsamkeit: Dry Tortugas, irgendwo im Golf von Mexiko

\n

Dabei gibt es keine Instrument an Bord, die auf diese Umstände hinweisen: Die Geschwindigkeit eines Flugzeugs wird an Bord des Flugzeugs relativ zur umgebenden Luft angezeigt. Das kann unter anderem dafür sorgen, dass Flugzeug eine fantastische Geschwindigkeit über dem Boden erreichen, die sie als wahre Geschwindigkeit relativ zur sie umgebenden Luft niemals überstehen würden. Und das ein starker Seitenwind das Flugzeug seitwärts vom Kurs drückt, verändert den Kompass an Bord ebenfalls nicht – ein Flugzeug muss nicht zwangsläufig in die Richtung zeigen, in die es fliegt. Das erleben Instrumentenflieger, wenn sie versuchen, auf einem eingestellten Radial zu bleiben.

\n

Dementsprechend ist es wichtig, vor dem Abflug (und sinnigerweise auch unterwegs) die genaue Windrichtung und -geschwindigkeit zu kennen, und die Auswirkung auf den eigenen Kurs einzukalkulieren. Während bei Gegen- und Rückenwind keine Auswirkungen auf den Kurs zu befürchten sind, sondern nur auf die Geschwindigkeit über dem Boden, ist die korrekte Einschätzung von Seitenwinden deutlich schwieriger. Mit ein bisschen angewandter Trigonometrie kann man diese Berechnungen selber durchführen – ich persönlich habe mir einen kleinen Rechner zum Berechnen des Korrekturkurses und der tatsächlichen Geschwindigkeit gebaut.

\n

Fazit

\n

Die Welt der GPS-Navigation und Autopiloten hat euch den Spaß am Fliegen genommen? Dann macht mit beim VFR. Selbst kurze Strecken werden zur fliegerischen Herausforderung – und ihr erlebt eine ganz neue Verbundenheit mit eurer Umgebung, da auf einmal jede Straße, jeder Fluss und jedes Gebirge relevant wird.

\"\"", - "summary": "Mein aktueller Lieblings-Flugsimulator Aerofly FS2 überredet mich immer wieder zu neuen Experimenten. Nachdem ich IFR ohne GPS in Aerofly FS2 ausprobiert…", - "date_published": "2019-09-22T18:43:14+02:00", - "date_modified": "2020-02-07T14:31:40+01:00", - "author": { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - }, - "authors": [ - { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - } - ], - "banner_image": "https://journal.3960.org/posts/2019-09-22-vfr-ohne-technischen-schnickschnack/sunset.jpg", - "language": "de-DE", - "image": "https://journal.3960.org/posts/2019-09-22-vfr-ohne-technischen-schnickschnack/sunset.jpg", - "tags": [ - "Aerofly FS2", - "Fliegerei", - "Geografie", - "Simulation", - "Spiel", - "X-Plane" - ], - "_geo": { - "about": "http://geojson.org/", - "type": "Point", - "coordinates": [ - -80.343874, - 25.279406 - ] - } - }, - { - "id": "user/posts/2019-09-16-f-16-fighting-falcon-fuer-dcs/index.md", - "url": "https://journal.3960.org/posts/2019-09-16-f-16-fighting-falcon-fuer-dcs/", - "title": "Die F-16 Fighting Falcon für DCS", - "content_html": "
\n

Endlich! Nachdem für den Digital Combat Simulator bereits jetzt eine riesige Palette an Fluggeräten erhältlich ist, produziert Eagle Dynamics nun selber den Klassiker unter den westlichen Kampfflugzeugen: Die F-16 Fighting Falcon aka „Viper“.

\n\n

Dieses Flugzeug befindet sich seit 1974 in Produktion, und wird auch heute nach über 40 Jahren immer noch hergestellt – wenn auch die neueren Ausgaben der F-16 Block 60 sich gegenüber dem Ursprungsmodell sowohl von der Leistung als auch von der Sensorik her stark weiter entwickelt haben. Von de F-16 wurden bis heute über 4.000 Einheiten hergestellt und in über 20 Ländern in Dienst gestellt.

\n

DCS hatte schon immer eine ansprechende Palette von westlichen Kampfflugzeugen: A-10 Warthog, F-5 Tiger, F-14 Tomcat, F-15 Eagle, F-18 Hornet, AV-8B Harrier. Aber die F-16 war und ist das ikonische Kampfflugzeug der NATO. Wenn also nichts dazwischen kommt, steige ich demnächst von meiner (kostenlosen) Su-25 Frogfoot auf die F-16 um.

\n

Ich persönlich war schon früh der (simulierten) F-16 verbunden: Schon auf dem Commodore C-64 hatte ich mit Digital Integrations F-16 Combat Pilot meine ersten Erfahrungen gemacht. Und obwohl ich die legendäre Spectrum Holobytes Falcon-Reihe nur kurz gestreift habe, so habe ich doch zumindest in Strike Commander (einem Ableger der Wing Commander-Serie) die F-16 (simuliert) geflogen. So oder so ist die F-16 ein in unzähligen Computerspielen und -simulationen auftretender Kampfjet.

\n

DCS bzw. Eagle Dynamics haben übrigens schon begonnen, Schulungsvideos bei Youtube einzustellen – damit zukünftige DCS-F-16-Piloten sich schonmal mit ihrem neuen Arbeitsgerät vertraut machen können.

\n
\"\"", - "summary": "Endlich! Nachdem für den Digital Combat Simulator bereits jetzt eine riesige Palette an Fluggeräten erhältlich ist, produziert Eagle Dynamics nun selber den…", - "date_published": "2019-09-16T18:01:22+02:00", - "date_modified": "2019-09-29T17:43:04+02:00", - "author": { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - }, - "authors": [ - { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - } - ], - "banner_image": "https://img.youtube.com/vi/8QfANpD0GHY/hqdefault.jpg", - "language": "de-DE", - "image": "https://img.youtube.com/vi/8QfANpD0GHY/hqdefault.jpg", - "tags": [ - "DCS", - "Fliegerei", - "Simulation", - "Spiel", - "Militär" - ] - }, - { - "id": "user/posts/2019-09-13-programmierung-zurueck-zur-werkbank/index.md", - "url": "https://journal.3960.org/posts/2019-09-13-programmierung-zurueck-zur-werkbank/", - "title": "Programmierung: Zurück zur Werkbank", - "content_html": "

Wie schon in dem Artikel „Simple & Boring“ von Chris Coyier hat auch Bastian Allgeier eine Lanze für Einfachheit in der Programmierung gebrochen. Sein Artikel „Simplicity (II)“ dürfte vielen altgedienten Programmierern aus der Seele sprechen.

\n

Tatsächlich bemerke ich sowohl in der privaten als auch beruflichen Programmierung den Trend, für mehr Geschwindigkeit ein neues Tool einzusetzen… das kleine Probleme verursacht, die durch ein weiteres Tool gelöst werden müssen… das kleine Probleme verursacht, die durch ein weiteres Tool gelöst werden müssen…

\n

When everything works, it feels like magic. When something breaks, it's hell.

\n
Bastian Allgeier, „Simplicity (II)
\n

Der Artikel dreht sich zwar primär darum, was diese Abhängigkeiten gerade für ältere Projekte bedeuten (nämlich, dass Abhängigkeiten nach ein paar Jahren sich nicht wieder auslösen lassen, weil die dafür benötigten Versionen an Tools nicht mehr zur Verfügung stehen), inzwischen bemerke ich aber auch bei aktuellen Projekten die Probleme, die übermäßige Abhängigkeiten für die Entwicklungsgeschwindigkeit bedeuten können, wenn auch nur ein Teil ausfällt.

\"\"", - "summary": "Wie schon in dem Artikel „Simple & Boring“ von Chris Coyier hat auch Bastian Allgeier eine Lanze für Einfachheit in der Programmierung gebrochen. Sein Artikel…", - "date_published": "2019-09-13T18:23:11+02:00", - "date_modified": "2019-10-17T18:51:03+02:00", - "author": { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - }, - "authors": [ - { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - } - ], - "banner_image": "https://cdn.3960.org/favicon-192x192.png", - "language": "de-DE", - "image": "https://cdn.3960.org/favicon-192x192.png", - "external_url": "https://bastianallgeier.com/notes/simplicity-part-2", - "tags": [ - "CSS", - "Javascript", - "Meinung", - "PHP", - "Programmierung", - "Webdevelop", - "Geckobar" - ] - }, - { - "id": "user/posts/2019-05-08-rueckkehr-von-b-und-i/index.md", - "url": "https://journal.3960.org/posts/2019-05-08-rueckkehr-von-b-und-i/", - "title": "Die Rückkehr von und ", - "content_html": "

Vor vielen Jahren haben Web-Entwickler <i>-, <b>- und <u>-Tags im HTML den Rücken gekehrt.

\n

In der Anfangsphase von HTML waren Tags und Attribute sowohl für Inhalt als auch Styling zuständig waren. Mit dem Aufkommen von CSS, der Idee von semantischen Layout und Barrierefreiheit wurden Tags aussortiert, die nur für Styling zuständig waren. Und so wurden auch <i>, <b> und <u> mit HTML4 gestrichen, und waren ab dort deprecated bzw. personae non gratae.

\n

Aber tatsächlich sind sie wieder da! In HTML5 wurden diese HTML-Tags mit neuer Bedeutung wieder eingeführt, und können wieder verwendet werden.

\n\n

Elemente, die regulär gefettet dargestellt werden

\n

Als Ersatz von <b> wurde <strong> propagiert. Tatsächlich haben aber weiterhin beide Tags ihre Relevanz. Das Mozilla Developer Network weiß das folgende über <strong> und <b> zu berichten:

\n

The HTML Strong Importance Element (<strong>) indicates that its contents have strong importance, seriousness, or urgency.

\n
MDN-Definition von <strong>
\n

The HTML Bring Attention To element (<b>) is used to draw the reader's attention to the element's contents, which are not otherwise granted special importance.

\n
MDN-Definition von <b>
\n

Nach dieser Definition wird <strong> für Wörter verwendet, die man bei Aussprache besonders betonen würde. <b> dagegen empfiehlt sich z.B. für die Hervorhebung von wichtigen Begriffen in einem Text.

\n

Elemente, die regulär kursiv dargestellt werden

\n

Für <i> sollte vor geraumer Zeit nur noch <em> verwendet werden. Aber auch hier hat sich ein Wandel vollzogen. Das Mozilla Developer Network hat folgende Information <em> und <i> parat:

\n

The HTML <em> element marks text that has stress emphasis.

\n
MDN-Definition von <em>
\n

The HTML <i> element represents a range of text that is set off from the normal text for some reason. Some examples include technical terms, foreign language phrases, or fictional character thoughts.

\n
MDN-Definition von <i>
\n

Dementsprechend wird <em> für Wörter verwendet, die bei Aussprache besonders betont werden würden. Mit <i> dagegen würden spezielle Begriffe und Wörter aus dem Text herausgehoben werden.

\n

Und sogar unterstrichene Elemente

\n

Selbst das verpönte <u> hat eine Wiederauferstehung erlebt:

\n

The HTML Unarticulated Annotation Element (<u>) represents a span of inline text which should be rendered in a way that indicates that it has a non-textual annotation.

\n
MDN-Definition von <u>
\n

…wobei im Beispiel von MDN die rote Unterkringelung von falsch geschriebenen Wörtern aufgeführt wird.

\n

Die Verwendung von <i> und <b> in Markdown

\n

In Markdown wird mit _Wort_ bzw. *Wort* immer ein <em>Wort</em>, mit __Wort__ bzw. **Wort** immer ein <strong>Wort</strong> erzeugt. Um <i> und <b> in Markdown zu erzeugen gibt es keine Symbole.

\n

Dafür erlaubt Markdown aber die Verwendung von HTML-Tags! Wenn in Markdown also <b>Wort</b> eingegeben wird, wird auch <b>Wort</b> ausgegeben. Demzufolge ist dies hier ein valider Markdown-Text:

\n
Hier kommt ein _kursives Wort_, gefolgt von einem *kursiven Wort*, beide mit `<em>` geschrieben.\n\nHier kommt ein __gefettetes Wort__, gefolgt von einem **gefetteten Wort**, beide mit `<strong>` geschrieben.\n\nHier dagegen kommt ein <i>kursives Wort</i>, mit `<i>` geschrieben.\n\nUnd hier kommt ein <b>gefettetes Wort</b>, mit `<b>` geschrieben.
\n

Fazit

\n

Viele von HTML-Entwicklern als Dogmen verstandene Leitsätze müssen immer wieder überprüft werden. Genau so wie die Vorstellung falsch ist, dass <table>- und <div>-Tags oder id-Attribute nicht verwenden werden dürften, ist die Verwendung von <i>, <b> und <u> im richtigen Kontext nicht nur erlaubt, sondern tatsächlich eine sehr gute Idee.

\"\"", - "summary": "Vor vielen Jahren haben Web-Entwickler -, - und -Tags im HTML den Rücken gekehrt.\nIn der Anfangsphase von HTML waren Tags und Attribute sowohl für…", - "date_published": "2019-05-08T19:10:30+02:00", - "date_modified": "2019-10-17T18:50:52+02:00", - "author": { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - }, - "authors": [ - { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - } - ], - "banner_image": "https://cdn.3960.org/favicon-192x192.png", - "language": "de-DE", - "image": "https://cdn.3960.org/favicon-192x192.png", - "tags": [ - "Blog", - "Programmierung", - "Webdevelop" - ] - }, - { - "id": "user/posts/2019-04-24-verantwortung-von-software/index.md", - "url": "https://journal.3960.org/posts/2019-04-24-verantwortung-von-software/", - "title": "Die Verantwortung von Software", - "content_html": "

Ein Tipp von den Wedeler Jungs: Gregory Travis erklärt aus seiner Erfahrung als Programmierer und Pilot, warum Flugzeugbau und Softwarebau zwei sehr unterschiedliche Grundphilosophien haben, die nicht gut zueinander passen – am Beispiel des Abstürze der Boeing 737 Max.

\n\n

Tatsächlich finde ich genug Beispiele: Das Smartphone hat einen größerer Bug? Keine Angst, es wird ein Update geben. Der Staubsauger-Roboter bleibt öfter an Teppich-Kanten hängen? Kein Problem, da kommt früher oder später ein Update. Die Rumble-Packs der WMR-Brille funktionieren nicht? Nach dem nächsten Update tun sie das bestimmt. In dem Spiel fehlen versprochene Features? Ach, das werden die Entwickler früher oder später schon nachliefern.

\n

Als Software-Entwickler verlassen wir uns sehr darauf, dass wir Fehler später immer noch korrigieren können. Wir verzichten auf Testing, QA, Methoden zur Fehlerbehandlung, Exception-Abarbeitung, Prüfung von Variablen, Quoting, saubere Typisierung – weil für uns als Entwickler nicht viel davon abhängt, als gegebenenfalls später ein Patch dafür bauen zu müssen. Zum Glück leben wir ja nicht mehr in der Zeit, in der Software auf einem Datenträger verteilt werden muss – oder auf ein Modul gebrannt wird, und dort bis in alle Ewigkeit funktionieren muss.

\n

Tatsächlich sollten wir als Programmierer etwas mehr Ehrfurcht vor unserer Aufgabe haben – und diese Ehrfurcht auch einfordern. Außerdem sollten wir uns selber einen defensiven Programmierstil auferlegen. In diesem Zusammenhang möchte ich nochmals auf Kalashnikov-Programmierung hinweisen:

\n

Die Programmierung muss nicht technisch herausragend sein – sie muss robust, unter jeder Umgebung einsatzbereit und einfach zu reparieren sein.

\n

Sie auch den Artikel über die die Vorzüge einfacher Programmierung.

\"\"", - "summary": "Ein Tipp von den Wedeler Jungs: Gregory Travis erklärt aus seiner Erfahrung als Programmierer und Pilot, warum Flugzeugbau und Softwarebau zwei sehr…", - "date_published": "2019-04-24T19:02:28+02:00", - "date_modified": "2019-04-24T19:02:28+02:00", - "author": { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - }, - "authors": [ - { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - } - ], - "banner_image": "https://cdn.3960.org/favicon-192x192.png", - "language": "de-DE", - "image": "https://cdn.3960.org/favicon-192x192.png", - "tags": [ - "Fliegerei", - "Geckobar", - "Programmierung", - "Technologie" - ] - }, - { - "id": "user/posts/2019-04-23-einfachheit-programmierung/index.md", - "url": "https://journal.3960.org/posts/2019-04-23-einfachheit-programmierung/", - "title": "Einfachheit in Programmierung", - "content_html": "

Meine persönlichen Grundsätze für Programmierung finden sich bestens in dem Artikel Simple & Boring zusammengefasst. Meine eigenen Ideen dazu:

\n

Wenn ich ein neues Tool, ein neues Konzept oder eine neue Sprache verwenden möchte, frage ich mich vorher: Welches Problem löst das? Außerdem eine gute Frage: Welche Probleme verursacht das?

\n

Außerdem verbreite ich gerne die Idee der Kalashnikov-Programmierung: Die Programmierung muss nicht technisch herausragend sein – sie muss robust, unter jeder Umgebung einsatzbereit und einfach zu reparieren sein.

\"\"", - "summary": "Meine persönlichen Grundsätze für Programmierung finden sich bestens in dem Artikel Simple & Boring zusammengefasst. Meine eigenen Ideen dazu:\nWenn ich ein…", - "date_published": "2019-04-23T19:16:18+02:00", - "date_modified": "2019-04-23T19:16:18+02:00", - "author": { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - }, - "authors": [ - { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - } - ], - "banner_image": "https://cdn.3960.org/favicon-192x192.png", - "language": "de-DE", - "image": "https://cdn.3960.org/favicon-192x192.png", - "external_url": "https://css-tricks.com/simple-boring/", - "tags": [ - "Geckobar", - "Meinung", - "Philosophie", - "Programmierung", - "Webdevelop" - ] - }, - { - "id": "user/posts/2019-04-10-bilder-iframes-einfach-mit-lazy-loading-ausstatten/index.md", - "url": "https://journal.3960.org/posts/2019-04-10-bilder-iframes-einfach-mit-lazy-loading-ausstatten/", - "title": "Bilder und iFrames einfach mit Lazy-Loading ausstatten", - "content_html": "

Lazy-loading ist eine beliebte Technik, um die gefühlte Geschwindigkeit einer Internetseite zu erhöhen. Statt alle Bilder einer Webseite schon beim Laden der Seite mitzuladen, werden nur die Bilder geladen, die auch tatsächlich sichtbar sind. Damit verringert man gerade auf langen Seiten die initial geladene Menge an Bildern.

\n

Bisher hatte das mit etwas Aufwand zu tun, und auf jeden Fall mit JavaScript. Netterweise gibt es da jetzt auch eine deutlich einfachere Lösung.

\n\n

Die bisherigen Lösungen gingen davon aus, dass irgendeine Form von LazyLoad-JavaScript auf der Seite montiert wurde, und das HTML eines jeden Bildes abgeändert werden musste:

\n
<!-- Load when visible -->\n<img src="example-lowres.jpg"\n  data-src="example-highres.jpg" class="lazyload"\n  alt="" width="240" height="240" />
\n

Google Chrome unterstützt in absehbarer Zukunft ein Attribut namens loading, dass das Ladeverhalten von <img> und <iframe> steuert, ohne zusätzliches Javascript. Genaue Details kann man einem Blog-Post eines Chrome-Entwicklers über Lazy-Loading entnehmen, im HTML sieht das aber schlicht und ergreifend wie folgt aus:

\n
<!-- Load when visible -->\n<img src="example.jpg" loading="lazy" alt="" width="240" height="240" />\n<iframe src="example.html" loading="lazy"></iframe>\n\n<!-- Load as soon as possible -->\n<img src="example.jpg" loading="eager" alt="" width="240" height="240" />\n<iframe src="example.html" loading="eager"></iframe>
\n

Damit entfällt in Zukunft in Google Chrome (und allen anderen Browsern, die da nachziehen werden) die Notwendigkeit, JavaScript für Lazy-Loading auf der eigenen Seite zu montieren. Zudem können browser ohne diese Möglichkeit bzw. ohne JavaScript immer noch die selben Inhalte sehen.

\n

Tests mit Chrome

\n

Solange Google Chrome das Feature noch nicht direkt unterstützt, muss man im aktuellen Google Chrome Lazy-Loading noch einmalig einschalten:

\n
    \n
  1. In Chrome chrome://flags in die URL-Zeile eingeben
  2. \n
  3. Nach „Lazy“ suchen.
  4. \n
  5. Die beiden nun angezeigten Optionen aktivieren.
  6. \n
  7. Chrome neu starten.
  8. \n
\n

Danach sollte man beim Besuch einer Seite, die mit loading-Attributen versehen ist, beim Öffnen des Inspektors bemerken, dass erst beim Scrollen auf der Seite weiter unten befindliche Bilder nachgeladen werden.

\n

Mit der Gießkanne: Umsetzung in NodeJs

\n

Um in einem gesamten Content-Block jedes <img> und <iframe> mit dem passenden loading-Attribut zu versehen, reicht folgende kleine Funktion:

\n
const lazyloadAttributes = function(html, loading = 'lazy') {\n  return html.replace(/(<(?:img|iframe) )/g, '$1loading="' + loading + '" ');\n};
\n

…und in PHP

\n

Gleichsam können in PHP z.B. redaktionelle Texte durch diese Funktion durchgeleitet werden, um überall Lazy-Loading hinzuzufügen:

\n
function lazyloadAttributes($html, $loading = 'lazy') {\n  return preg_replace('/(<(?:img|iframe) )/is', '$1loading="' . $loading . '" ', $html);\n}
\n

Fazit

\n

Eigentlich gibt es wenig Gründe, dass Attribut nicht einzusetzen. Gerade auf länglichen Übersichtsseiten kann das Erlebnis für den Besucher deutlich verbessert werden. Und für Mobilgeräten mit geringer Bandbreite kann der Geschwindigkeitszuwachs immens sein.

\n

Außerdem steht zu erwarten, dass in Zukunft auch Safari und vielleicht sogar Firefox dieses Feature unterstützen werden – während Microsofts Edge ja gerade auf die Browser-Engine von Chrome wechselt, und damit dieses Feature implizit unterstützt.

\"\"", - "summary": "Lazy-loading ist eine beliebte Technik, um die gefühlte Geschwindigkeit einer Internetseite zu erhöhen. Statt alle Bilder einer Webseite schon beim Laden der…", - "date_published": "2019-04-10T19:02:20+02:00", - "date_modified": "2019-04-20T09:57:01+02:00", - "author": { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - }, - "authors": [ - { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - } - ], - "banner_image": "https://cdn.3960.org/favicon-192x192.png", - "language": "de-DE", - "image": "https://cdn.3960.org/favicon-192x192.png", - "tags": [ - "Für Facebook", - "Javascript", - "Programmierung", - "Webdevelop" - ] - }, - { - "id": "user/posts/2019-03-27-wettergeraet-fuer-aerofly-fs2/index.md", - "url": "https://journal.3960.org/posts/2019-03-27-wettergeraet-fuer-aerofly-fs2/", - "title": "Das Wettergerät für Aerofly FS2", - "content_html": "

\"\" Was Anfang des Jahres als kleine Idee begonnen hatte, ist inzwischen in einem einigermaßen komplexen Projekt gemündet: Ich wollte das Wetter in meinem Lieblings-Flugsimulator Aerofly FS2 verbessern.

\n\n

Aerofly FS2 ist noch weit entfernt von der Tiefe und Komplexität von X-Plane oder auch Prepar3D. Darum hatte ich mir zwischenzeitlich X-Plane angeschaut, war aber inzwischen bereits sehr verwöhnt von der Performance und Unkompliziertheit von Aerofly FS2. Gerade die Unterstützung für Virtual Reality gefiel mir in AFS2 einfach besser, so dass ich X-Plane inzwischen wieder in den Hangar gerollt hatte.

\n

Nach meiner Rückkehr zu AFS2 vermisste ich nun aber doch ein paar Features:

\n
    \n
  1. Air Traffic Control, d.h. die korrekte Verwendung des Funkgerätes,
  2. \n
  3. eine lebendig wirkende Umwelt,
  4. \n
  5. und den realen Bedingungen entsprechendes Wetter.
  6. \n
\n

Während die lebendig wirkende Umwelt (in Form von Autos und Schiffen) gerade durch das Aerofly Life Project angegangen wird, habe ich mir also das Wetter vorgenommen.

\n

Tatsächlich existiert in X-Plane eine Idee, die in Aerofly FS2 ebenfalls funktioniert: Jeder größere Flughafen auf diesem Planeten veröffentlicht in relativ kurzen Abständen seinen aktuellen Wetterbericht in Form eines METAR. Dieser Wetterbericht ist nicht nur sehr verdichtet, sondern auch mit etwas Geschick maschinenlesbar.

\n
KEYW 261153Z 36005KT 10SM FEW012 23/23 A3004 RMK AO2 SLP172 T02330217 10233 20222 53012
\n

Darüber hinaus gibt es viele Anlaufstellen, die METARs über eine HTTP-Schnittstelle zur Verfügung stellen. Die Quelle war also gefunden.

\n

Gleichzeitig hatte ich in der Hauptkonfigurationsdatei main.mcf von AFS2 entdeckt, dass die Wetterdaten darin gespeichert wurden. Zu meiner Überraschung fanden sich dort sogar Schalter, die in der Simulation selber gar nicht konfigurierbar waren. Mit ein paar wenigen Experimenten konnte ich nachweisen, dass Veränderungen in der main.mcf tatsächlich in AFS2 übernommen wurden – hier war also mein Ziel.

\n

Die Aufgabe

\n

Die Aufgabe war jetzt also klar erkennbar:

\n
    \n
  1. Einen METAR für einen definierbaren Flughafen per HTTP-API abholen,
  2. \n
  3. den METAR-Bericht in ein strukturiertes METAR-Objekt umwandeln,
  4. \n
  5. das METAR-Objekt wiederum in ein Aerofly-kompatibles Objekt umwandeln,
  6. \n
  7. und diese Daten dann in die Simulation schreiben – hier also in die main.mcf.
  8. \n
\n

Phase 1: Kommandozeile

\n

\"\"

\n

Als Webprogrammierer habe ich mich für das Projekt an eine Sprache gehalten, die ich gut kannte: JavaScript, bzw. NodeJS.

\n

Hier zahlte sich vor allen Dingen aus, dass ich privat sehr gerne test-getrieben entwickele, da das Format von METARs doch einige Überraschungen parat hatte. Unzählige Tests später hatte ich dann aber einen METAR-Parser, der mit vielen Fallstricken der METAR-Angaben umgehen konnte.

\n

Schon nach einem Monat kopierte das Kommandozeilen-Tool fröhlich METAR-Daten in Aerofly FS2. Das ganze Projekt veröffentliche ich als „Aerofly-Weather“ bzw. „AeroWX“ bei NPM.

\n

Schon bald fiel mir aber auf, dass ich zu kurz gesprungen war: Einerseits erforderte mein Programm die Installation von NodeJS – andererseits war es ein Kommandozeilenprogramm, mit entsprechend wenig ansprechender Präsentation.

\n

Phase 2: Electron

\n

\"\"

\n

Es musste also eine bedienbare Desktop-Applikation her. Da ich als Webprogrammierer mit dieser Welt bisher nur sehr wenig Erfahrung hatte, fand ich zum Glück eine Lösung genau nach meinem Geschmack: Electron.

\n

Mit Electron konnte ich nicht nur meine NodeJS-Programmierung direkt weiterverwenden, das GUI meiner Applikation konnte mit HTML, CSS und JavaScript gebaut werden – Sprachen, mit denen ich jeden Tag arbeite. So konnte mit relativ moderatem Aufwand nach einem Monat eine Desktop-Applikation gebaut werden. Diese wurden mit dem Electron-Builder zu einer eigenständigen EXE-Datei kompiliert und ließ sich dann als „Aerofly-Weather“ bzw. „AeroWX“ bei Github herunterladen.

\n

Aber auch hier nagte die Unzufriedenheit an mir: Diese Applikation hatte als ZIP gepackt eine Größe von über 60MB, und auch die Ausführung dieses Programms verschlang Unmengen an RAM. Für eine so kleine Applikation war dies kaum zu rechtfertigen.

\n

Phase 3: Kommandozeile, aber in C++

\n

\"\"

\n

Durch einen Tipp aus der Aerofly-Community hatte man mir einen neuen Floh ins Ohr gesetzt: Eine Umsetzung in C++. Damit könnte ich den Nutzer von der Last befreien, entweder NodeJS zu installieren oder sich eine immens große Electron-App herunterzuladen. Nach ein bisschen Recherche und Interviews mit befreundeten Programmierern stürzte ich mich erneut ins Unterholz, um das Projekt nochmals zu bauen – nur diesmal in C++.

\n

Praktischer Nebeneffekt war, dass ich alle funktionalen Überlegungen bereits in NodeJS gelöst hatte. In meiner Vorstellung konnte ich mich also ganz auf das Umschreiben meines Projekts von JavaScript auf C++ konzentrieren. Tatsächlich waren ein Großteil der Ideen in C++ reproduzierbar, wenn auch die strenge Typisierung (als PHP- und JavaScript-Programmierer eine ganz neue Erfahrung) und die nun notwendige IDE (Microsoft Visual Studio) doch zuerst eine erhebliche Hürde darstellte.

\n

Nichtsdestotrotz konnte nach knapp zwei Wochen das ursprüngliche Projekt als eigenständig ausführbare Applikation umgesetzt werden. Die Ausgabe auf der Kommandozeile war beinahe identisch – die Größe der eigentlichen Applikation aber deutlich kompakter, und vor allen Dingen die Installation von NodeJS nicht mehr erforderlich. Mit einem einfachen Deployment-Workflow konnte die Applikation als „Aerofly Wettergerät“ bei Github bereitgestellt werden.

\n

Phase 4: Eine echte Desktop-Applikation

\n

\"\"

\n

Das Aerofly Wettergerät brauchte nun ebenfalls einen feschen Desktop-Aufsatz. Hier hatte ich zuerst Sciter ausprobiert – ähnlich wie Electron hätte man HTML und CSS für die Gestaltung des Frontends verwenden können. Nach einigen Tests erwies sich aber Sciter für meine Belange als wenig geeignet. Stattdessen war der Tipp eines Kollegen goldrichtig: WxWidgets erlaubte mit überschaubarem Aufwand von zwei weiteren Wochen, eine native Applikation im Look & Feel des Betriebssystems zu erstellen.

\n

Die so entstandene Desktop-Applikation wurde ebenfalls im Paket vom „Aerofly Wettergerät“ bei Github bereitgestellt, so dass sowohl die Kommandozeilen- als auch die Desktop-Version nicht nur die selben Sourcen haben, sondern auch im selben Installationspaket geliefert werden. Die Download-Größe liegt unter einem Zehntel des ursprünglichen NodeJS-Paketes, und auch der Fußabdruck im RAM ist um Größenordnungen kleiner – wenn auch die Ausgabe nun etwas schlichter wirkt.

\n

Die nächste Phase?

\n

Bis jetzt verbleibt ein Wehrmutstropfen bei dem Projekt: Das Wetter kann nur außerhalb der Simulation heruntergeladen und eingestellt werden. Damit bleiben folgende Fälle außen vor:

\n
    \n
  • Nach dem Starten der Simulation unternimmt man einen längeren Flug, bei dem sich sowohl aufgrund der geografischen Position als auch der voranschreitenden Zeit das Wetter verändert hätte.
  • \n
  • Nach dem Starten des Simulators entscheidet man sich spontan für einen ganz anderen Start- bzw. Zielflughafen.
  • \n
\n

Meine Überlegungen dazu waren, mit dem von IPACS angebotenen SDK eine eigene DLL zu bauen, die alle zehn Minuten den nächstgelegenen Flughafen sucht, das Wetter herunterlädt und dann in die laufende Simulation importiert. Da für diese Idee die Schnittstellen in die Simulation aber aktuell nicht ausreichend dokumentiert sind bzw. auch nicht existieren, liegt das Projekt erstmal auf unbestimmte Zeit auf Eis.

\"\"", - "summary": "Was Anfang des Jahres als kleine Idee begonnen hatte, ist inzwischen in einem einigermaßen komplexen Projekt gemündet: Ich wollte das Wetter in meinem…", - "date_published": "2019-03-27T19:32:27+01:00", - "date_modified": "2020-02-11T08:52:46+01:00", - "author": { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - }, - "authors": [ - { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - } - ], - "banner_image": "https://journal.3960.org/posts/2019-03-27-wettergeraet-fuer-aerofly-fs2/favicon-512x512.png", - "language": "de-DE", - "image": "https://journal.3960.org/posts/2019-03-27-wettergeraet-fuer-aerofly-fs2/favicon-512x512.png", - "tags": [ - "Aerofly FS2", - "Fliegerei", - "API", - "Für Tumblr", - "Programmierung", - "Simulation", - "Spiel" - ] - }, - { - "id": "user/posts/2019-02-28-ueber-impertinenz/index.md", - "url": "https://journal.3960.org/posts/2019-02-28-ueber-impertinenz/", - "title": "Über Impertinenz", - "content_html": "

Wer sich einen impertinenten Tonfall leistet, sollte sich seiner Sache besser sicher sein.

\n

Besser ist es natürlich, Recht zu haben und ein angenehmer Gesprächspartner zu bleiben.

\"\"", - "summary": "Wer sich einen impertinenten Tonfall leistet, sollte sich seiner Sache besser sicher sein.\n\nBesser ist es natürlich, Recht zu haben und ein angenehmer Gespr…", - "date_published": "2019-02-28T18:46:14+01:00", - "date_modified": "2019-02-28T18:46:14+01:00", - "author": { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - }, - "authors": [ - { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - } - ], - "banner_image": "https://cdn.3960.org/favicon-192x192.png", - "language": "de-DE", - "image": "https://cdn.3960.org/favicon-192x192.png", - "tags": [ - "Idee", - "Meinung", - "Philosophie" - ] - }, - { - "id": "user/posts/2019-02-14-simplex-duplex/index.md", - "url": "https://journal.3960.org/posts/2019-02-14-simplex-duplex/", - "title": "Simplex, Duplex", - "content_html": "
    \n
  • Simplex: Es funktioniert nur eine Richtung gleichzeitig.
  • \n
  • Duplex: Es funktionieren beide Richtungen gleichzeitig.
  • \n
  • Durex: Es funktioniert keine Richtung… in 99% der Fälle jedenfalls.
  • \n
\"\"", - "summary": "Simplex: Es funktioniert nur eine Richtung gleichzeitig.\nDuplex: Es funktionieren beide Richtungen gleichzeitig.\nDurex: Es funktioniert keine Richtung… in 99%…", - "date_published": "2019-02-14T19:11:34+01:00", - "date_modified": "2019-02-14T19:11:34+01:00", - "author": { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - }, - "authors": [ - { - "name": "Frank Boës", - "url": "mailto:info@3960.org", - "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca" - } - ], - "banner_image": "https://cdn.3960.org/favicon-192x192.png", - "language": "de-DE", - "image": "https://cdn.3960.org/favicon-192x192.png", - "tags": [ - "Lustiges", - "Programmierung", - "Webdevelop" - ] - } - ] -} \ No newline at end of file diff --git a/RSParser/Tests/RSParserTests/Resources/489.rss b/RSParser/Tests/RSParserTests/Resources/489.rss deleted file mode 100644 index 3f6b82971..000000000 --- a/RSParser/Tests/RSParserTests/Resources/489.rss +++ /dev/null @@ -1,149 +0,0 @@ - - - <![CDATA[Stories by Hodl Hodl on Medium]]> - - https://medium.com/@hodlhodl?source=rss-b1f3d322dadf------2 - - https://cdn-images-1.medium.com/fit/c/150/150/1*PJ4xeTc0v0DOWgJIb25k8Q.jpeg - Stories by Hodl Hodl on Medium - https://medium.com/@hodlhodl?source=rss-b1f3d322dadf------2 - - Medium - Thu, 22 Nov 2018 05:50:04 GMT - - - - - <![CDATA[Hodl Hodl closes funding round]]> - https://medium.com/@hodlhodl/hodl-hodl-closes-funding-round-417d97952d42?source=rss-b1f3d322dadf------2 - https://medium.com/p/417d97952d42 - - - - Mon, 19 Nov 2018 08:10:26 GMT - 2018-11-19T08:42:59.267Z - For a long time Hodl Hodl remained self-funded. It was our own money that paid for the development and the team. This year we realized we needed to grow and move forward faster so we decided to start looking for external funding. We initially talked to a number of VCs and after months of negotiations it became obvious this wouldn’t work. Some declined us, some were constantly putting us on hold. We figured it’s just the way it works with VCs (read an awesome article “How to raise money” by Paul Graham on the subject, very helpful if you’re raising money for the first time).

In retrospect, we don’t really think VC funding would’ve been the right fit for a Bitcoin-company like ours. We don’t do KYC/AML and we’re not bending and selling our customers (and not because we’re so good and honest, but because that’s part of the value we provide and, thus, part of our business model) — and, if we took that VC money, our feeling was, we’d start bending.

Instead, we were very lucky to be approached by a number of bitcoiners who attended our “Baltic Honeybadger” conference, know us personally and decided that they wanted to invest in Hodl Hodl. And so they did.

So today, we’re proud to announce that WhalePanda, Ambroid, Marsmensch and two more undisclosed persons became the first investors of Hodl Hodl.

In 2019 Hodl Hodl will be:

  1. Improving the P2P Exchange platform
  2. Releasing P2P prediction contracts market (aka P2P Bitcoin Futures)
  3. Releasing a number of other stealth features that will hopefully be incredibly useful to the Bitcoin economy.

In general, Hodl Hodl will become THE platform for multisig contracts on Bitcoin.

We would like to thank all our investors for believing in us and we hope to not only deliver what is expected of us, but become good friends with them all over time. They’ve been really helpful and were encouraging us from Hodl Hodl’s launch so we duly appreciate that.

And finally, some good news for our customers: we’ll lower the exchange commission to 0.4% for December 2018, until the end of the year.

]]>
-
- - <![CDATA[Update on Hodl Hodl: New payment type “Cryptocurrency”]]> - https://medium.com/@hodlhodl/update-on-hodl-hodl-new-payment-type-cryptocurrency-ca7b7ab94b2f?source=rss-b1f3d322dadf------2 - https://medium.com/p/ca7b7ab94b2f - - - Fri, 09 Nov 2018 14:40:34 GMT - 2018-11-09T14:40:34.747Z - Dear Hodlers,

We want introduce to you an update at Hodl Hodl — we have added a new payment type “Cryptocurrency”.

Quick summary

From now on, every user can buy and sell Bitcoin and Litecoin, for other cryptocurrencies at Hodl Hodl.

About

When creating an offer, every user has to specify the payment method(-s) that he will accept. And every payment method is tied to a specific payment type: for example, payment methods “SWIFT” and “SEPA” are tied to the payment type “Bank wire”.

With this update, in the list of payment types, you can find a new one: “Cryptocurrency”.

It is tied to different payment methods such as: “Dash”, “Monero”, “Ethereum”, etc., which can be used to buy and sell your bitcoins and litecoins.

Further plans

We also have plans related to cryptocurrencies as payment methods. In the future, our customers will be able to set prices in terms of cryptocurrency, not fiat, as it is currently.

So you could create a Bitcoin buy or sell offer, for the price of 60 monero’s per 1 Bitcoin.

Propose a payment method

In case you don’t see the cryptocurrency you need in the list, you can propose one in the page “Payment methods” (which you can find in the user menu in the right top corner).

Once administrator approves your request — you will able to create an offer with the newly added cryptocurrency.

Previously

We had a payment method “Cryptocurrency” related to the payment type “Online payment system”, and with this update, we have deleted this payment method and disabled all offers using it.

Therefore, if you were the owner of such an offer, we kindly ask you to update your offer(-s), please set new payment methods using the exact name of the cryptocurrency you want, and don’t forget, you can set multiple payment methods in the same offer.

Reach us via

Hodl!

]]>
-
- - <![CDATA[Introducing a new feature: Lower exchange fees for staying online]]> - https://medium.com/@hodlhodl/introducing-a-new-feature-lower-exchange-fees-for-staying-online-302906f8b49f?source=rss-b1f3d322dadf------2 - https://medium.com/p/302906f8b49f - - - - - - - Thu, 08 Nov 2018 14:02:57 GMT - 2018-11-08T14:02:57.512Z - Dear Hodlers,

Today we introduce a new feature at Hodl Hodl — lower exchange fees for staying online.

Quick summary

Based on feedback from our users, we understand that everyone wants to see other users online at Hodl Hodl, otherwise, when starting a trade, nobody knows when it will advance from there.

So from now on, every user will get an exchange fee discount for staying online.

Description

Starting from today, Hodl Hodl now gives a discount of 20% off the exchange fee for being online for at least 8 hours within the past 24 hours.

We placed a timer in every user’s dashboard, which indicates how much time you need to be online until your discount is applied. When you reach the required time online — we’ll indicate in your dashboard that you are receiving the exchange fee discount.

Please be advised, the “time online discount” is applied at the moment the trade starts.

Reach us via

Hodl!

]]>
-
- - <![CDATA[Hodl Hodl announces OTC trading desk and brokerage company Tenbagger]]> - https://medium.com/@hodlhodl/hodl-hodl-announces-otc-trading-desk-and-brokerage-company-tenbagger-fed4be91db6?source=rss-b1f3d322dadf------2 - https://medium.com/p/fed4be91db6 - - - - - - - Fri, 02 Nov 2018 14:41:47 GMT - 2018-11-02T14:41:47.679Z -

We are proud to announce our OTC trading desk!

From now on, Hodl Hodl, in partnership with our EU licensed broker Tenbagger, will match and guide counter-parties, allowing you to buy and sell bitcoins with same day settlement, at the most competitive rates.

There has been a vibrant OTC Bitcoin trading market operating in parallel to the existing exchanges, but none of them are offering non-custodial escrow services for cryptocurrencies which would eliminate the risk of losing funds.

Hodl Hodl has developed non-custodial cryptocurrency escrow that leverages the full potential of Bitcoin’s blockchain, and ensures the highest level of security. For each trade, we create a unique multisig escrow account on Bitcoin’s blockchain, ensuring transparency and the highest level of security.

This also doesn’t affect our current operations. We are still non-KYC/AML non-custodial P2P exchange suitable for any type of trade — small or large. We are sticking with our original plan to help people buy Bitcoin/Litecoin in an easy and secure way.

Why choose OTC trading with us?

- Secure transactions

- Competitive rates

- Same day settlement

- Private and personalized trading experience

- 24/7 personalized support

To contact us:

Hodl!

]]>
-
- - <![CDATA[Hodl Hodl introduces 2 out of 3 multisig escrow]]> - https://medium.com/@hodlhodl/hodl-hodl-introduces-2-out-of-3-multisig-escrow-b2110580e036?source=rss-b1f3d322dadf------2 - https://medium.com/p/b2110580e036 - - - - - - - Mon, 22 Oct 2018 14:38:05 GMT - 2018-10-22T14:38:05.903Z -

Hey Hodlers,

Today Hodl Hodl, a P2P cryptocurrency exchange, introduces a new type of multisig escrow account available for contracts at the exchange. From now on, during offer creation, every user will choose what type of contract will be created by the offer: 2-out-of-2 or 2-out-of-3.

About

We have recently added a new type of escrow, and it’s already available for trades, it requires 2 out of 3 keys for making a release. This means, that from now on, in 2 out of 3 contracts, buyers have some control over the funds locked in escrow, since one of the keys belongs to himself, and the other two belong to the seller and Hodl Hodl. Also, 2 out of 2 contracts are still available for trading.

How does it work?

In every trade, Hodl Hodl exchange generates a unique multisig escrow cryptocurrency address, where the seller locks the funds, and then buyer sends the payment. Only after seller has received the payment, he releases funds from escrow directly to the buyers wallet.

In a regular 2 out of 3 contract, where everything goes well, buyer’s key is not needed — it only comes into play if the contract was disputed, and Hodl Hodl administrator resolved it in favor of buyer. In this case, buyer is able to sign a release transaction with his key and receive the funds without seller’s participation. This is how the 2 out of 3 contract type works.

Differences between these types of contracts

Now, you may have a question: what does the term “contract type” mean? Contract type indicates how many keys are needed to make a release from escrow:

  • Contract type “2 out of 2”: this means, that in contracts of this type, 2 keys are needed for a release, and in total there are 2 keys to escrow: one belongs to the seller, and one to Hodl Hodl.
  • Contract type “2 out of 3”: this means, that in contracts of this type, 2 keys are needed for a release, and in total there are 3 keys to escrow: one belongs to the seller, one to the buyer, and one to Hodl Hodl.

During the offer creation process, every user specifies what type of contract will be created with this specific offer. Once the offer is created, the contract type will be shown in the offer list for other users.

In case you already had offer(-s) before today, these contract types will remain 2 out of 2, so if you want, you can now edit your offer(-s).

Reach us via

Hodl Hodl exchange: hodlhodl.com

TESTNET Hodl Hodl exchange: testnet.hodlhodl.com

E-mail: support@hodlhodl.com

Blog: https://medium.com/@hodlhodl

Twitter: https://twitter.com/hodlhodl

Telegram: https://t.me/HodlHodl

Reddit: https://www.reddit.com/r/hodlhodl

Slack: https://goo.gl/zaMnCn

Facebook: https://www.facebook.com/hodlexchange

YouTube: https://www.youtube.com/channel/UCgujEoZqX_FfDTLb3Uuhsdg

Hodl!

]]>
-
- - <![CDATA[Introducing a new feature: Chat attachments]]> - https://medium.com/@hodlhodl/introducing-a-new-feature-chat-attachments-bb0d749b4381?source=rss-b1f3d322dadf------2 - https://medium.com/p/bb0d749b4381 - - - - - - - Fri, 05 Oct 2018 14:53:27 GMT - 2018-10-05T14:58:45.675Z - Introducing a new feature: Chat attachments

Dear all,

Today we are introducing a new feature at Hodl Hodl — chat attachments.

Quick summary

After receiving requests from our users, we’ve decided to add a new feature. At first glance, it seems to be a minor one, but it is crucial for trading cryptocurrencies via a P2P platform — to allow sending files in the contract’s chat window. Chat attachments would be very beneficial in case of dispute, users will have an opportunity to exchange information quick, which will reduce the time of solving the dispute.

Description

Each user now has the “paper clip” icon in the contract chat window, by clicking it, you can now send files to the chat.

An important feature to consider is that you may send an attachment to the administrator only, meaning your counterparty cannot see it — you may do so by leaving the checkbox “Show attachment to counterparty” blank.

Conversely, you must check the box to send an attachment to your counterparty.

Reach us via

Hodl Hodl exchange: hodlhodl.com

TESTNET Hodl Hodl exchange: testnet.hodlhodl.com

E-mail: support@hodlhodl.com

Blog: https://medium.com/@hodlhodl

Twitter: https://twitter.com/hodlhodl

Telegram: https://t.me/HodlHodl

Reddit: https://www.reddit.com/r/hodlhodl

Slack: https://goo.gl/zaMnCn

Facebook: https://www.facebook.com/hodlexchange

YouTube: https://www.youtube.com/channel/UCgujEoZqX_FfDTLb3Uuhsdg

Hodl!

]]>
-
- - <![CDATA[Hodl Hodl’s upcoming features]]> - https://medium.com/@hodlhodl/hodl-hodls-upcoming-features-854248d843a5?source=rss-b1f3d322dadf------2 - https://medium.com/p/854248d843a5 - - - - - - - Fri, 28 Sep 2018 13:59:06 GMT - 2018-09-28T13:59:06.252Z - body[data-twttr-rendered="true"] {background-color: transparent;}.twitter-tweet {margin: auto !important;}]]> - - - <![CDATA[Highlights of Baltic Honeybadger 2018]]> - https://medium.com/@hodlhodl/highlights-of-baltic-honeybadger-2018-d920a791d0ab?source=rss-b1f3d322dadf------2 - https://medium.com/p/d920a791d0ab - - - - - - Wed, 26 Sep 2018 14:05:32 GMT - 2018-09-26T14:05:32.621Z - body[data-twttr-rendered="true"] {background-color: transparent;}.twitter-tweet {margin: auto !important;}]]> - - - <![CDATA[Baltic Honeybadger 2018]]> - https://medium.com/@hodlhodl/baltic-honeybadger-2018-1c70eaa53d5c?source=rss-b1f3d322dadf------2 - https://medium.com/p/1c70eaa53d5c - - - - - - - Fri, 21 Sep 2018 13:34:11 GMT - 2018-09-21T13:34:54.429Z - ]]> - - - <![CDATA[Introducing a new feature: offer balance]]> - https://medium.com/@hodlhodl/introducing-a-new-feature-offer-balance-341c9e3ff0e7?source=rss-b1f3d322dadf------2 - https://medium.com/p/341c9e3ff0e7 - - - - - - - Wed, 19 Sep 2018 13:26:44 GMT - 2018-09-19T13:29:51.979Z - Introducing a new feature: the offer balance

Dear all,

Today we introduce to you a new feature at Hodl Hodl — the offer balance.

Quick summary

Some users may have a certain amount of fiat or cryptocurrency that they’re willing to trade at Hodl Hodl, so we decided to add an additional field to the offer creation form — where our customers can specify the total amount they are willing to trade through an offer.

Description

When creating an offer, every user is now able to specify the offer balance — the total amount of fiat that the offer creator is willing to trade. This is the max amount that may be purchased through the offer, in terms of fiat, or if it’s a sell offer, in terms of cryptocurrency (in fiat equivalent).

The balance is reduced by the amount of each completed contract through the offer, and when the remaining balance has been expended, the offer becomes inactive unless the user updates this field.

Reach us via

Hodl!

]]>
-
-
-
\ No newline at end of file diff --git a/RSParser/Tests/RSParserTests/Resources/4fsodonline.atom b/RSParser/Tests/RSParserTests/Resources/4fsodonline.atom deleted file mode 100644 index d715d7015..000000000 --- a/RSParser/Tests/RSParserTests/Resources/4fsodonline.atom +++ /dev/null @@ -1 +0,0 @@ -tag:blogger.com,1999:blog-39057418451756030232014-10-05T01:28:05.849-07:004 Fighting Serpents of DeathAnthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.comBlogger28125tag:blogger.com,1999:blog-3905741845175603023.post-4255411257634485922007-12-14T23:15:00.000-08:002007-12-15T11:35:51.128-08:004FSOD Documentary: Bloopers and StuffEven the 4 Fighting Serpents of Death don't always get it right on the first take!<br /><br /><br /><br /><br /><iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dyq9-5I_xeQrLuRdB8sMNk5uydYCcrUYCEeQM4rHZ8ittUFTmgvQzVVvwDQgWSD8DGPicg9Zx4Awulnbnqqnw' class='b-hbp-video b-uploaded' FRAMEBORDER='0' />Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com0tag:blogger.com,1999:blog-3905741845175603023.post-65523244188113060302007-12-14T00:54:00.000-08:002007-12-14T08:31:57.100-08:004FSOD The Documentary: Deleted ScenesHere are some of the scenes that didn't make the final cut,<br />and not because they weren't funny!<br /><br /><iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dzPs-XUg0wXFaUX91N90sd-kp9RGQd1tcx6Ijdw1VxRpFbgVbyrNAqmuErdrx5K_25BfMQcHVkCa2sKYMe6kA' class='b-hbp-video b-uploaded' FRAMEBORDER='0' />Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com0tag:blogger.com,1999:blog-3905741845175603023.post-48599265246991760662007-12-12T21:13:00.000-08:002007-12-12T22:18:44.752-08:00Special Feature: Jamie's PresentSynopsis:<br />Anthony has finally picked out the perfect present for his girlfriend, Jamie, tickets to an amazing concert! While Anthony sleeps though, a mysterious thief breaks into his home intended to rob him of his most valuable possessions. This thief does not realize that he has sealed his own doom by taking Jamie's Present. Upon realizing that he has been robbed, Anthony springs into action. With the help of an unlikely irish friend and a little cunningness of his own, Anthony finally tracks down Jamie's Present. The ultimate villian is a strange and dangerous Chinaman who challenges Anthony to a battle of wits, but Anthony has a couple tricks of his own up his sleeve. See how it all ends in Jamie's Present!<br /><br />Additional Information:<br />This film was produced in December of 2006 and is quite a little gem. Having completed production with only a Polaroid camera and half a roll of carbon copy paper, this film is a nice representation of just how resourceful 4FSOD Productions is. The heartfelt acting and emotionally driven storyline made this film a top seller for spouses to each other in 2006, outselling other blockbuster releases like Little Miss Sunshine, The Last Kiss, and Jackass 2.<br />A top seller, that is, among other pirated movies being sold out of trunks in neigborhood strip malls.<br /><br /><br /><iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dxg10UdzjPBh1LL62iekZ4COODDOO9owicQOz8MUenEgE5OizjKAO8bVc4At5Uvqyt-rkgdcelp3nATzkkj2w' class='b-hbp-video b-uploaded' FRAMEBORDER='0' />Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com0tag:blogger.com,1999:blog-3905741845175603023.post-17379353446679601782007-12-05T20:12:00.000-08:002007-12-05T20:58:51.202-08:00Chapter 12: Closing Credits<p>Congratulations! You have made it to the end of the documentary. Have no fear though, the saga of the 4<span class="blsp-spelling-error" id="SPELLING_ERROR_0">FSOD</span> is not even close to being over yet!</p><p> </p><p><iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dw-LyHre18Wte4cOXmZOddnlQPmEp01uEc4xP1VSAPHI20cstpqZpF7gBaXj4LeUwMNKR8TzR6umLRRJA_DpQ' class='b-hbp-video b-uploaded' FRAMEBORDER='0' /></p>Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com1tag:blogger.com,1999:blog-3905741845175603023.post-71188947587385081132007-12-04T21:07:00.001-08:002007-12-04T21:30:06.200-08:00Chapter 11: DenoumentThis is the documentary round up guys, thanks for watching! I hope you totally<br />enjoyed it!<br /><br /><iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dzQ_qyvm9VJvpfJ5LtNonHfdT72V04aBWSMzqBa9Rpkcf79kypEgCvXPTQwx31rQADaTAytCGkGWBzhdIyxbg' class='b-hbp-video b-uploaded' FRAMEBORDER='0' />Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com0tag:blogger.com,1999:blog-3905741845175603023.post-48500928774958630222007-11-28T22:10:00.001-08:002007-11-28T22:41:14.219-08:00Chapter 10: Got Smoke?Here's the infamous smoking episode where Brian is confronted<br />with his secret habit.<br /><br /><iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dzggey93fCR2yRaqAwBrghZr5mfZHOcweTASdxH-fA2ifuxvahUQLsCwUcvrfeMf1L62u5Ki_hnpIIsEtvTvw' class='b-hbp-video b-uploaded' FRAMEBORDER='0' />Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com0tag:blogger.com,1999:blog-3905741845175603023.post-69894509218073148552007-11-28T17:23:00.000-08:002007-11-28T18:29:21.621-08:00Chapter 9: Back on Track<iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dyyEcqQ9e6ZXDpY6pPLHHUlG1Vwhaw2e9LnNEJM7EOZ-MEJ1H7ZAzEALpJvG2up1T_HOlHy3w8sW0cS31Y19Q' class='b-hbp-video b-uploaded' FRAMEBORDER='0' />Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com0tag:blogger.com,1999:blog-3905741845175603023.post-67420328211917161692007-11-27T22:53:00.000-08:002007-11-27T23:29:14.452-08:00Chapter 8: Hits and MissesBack to the show, Guys!<br /><br /><iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dxC_InZ84hL0_WYLXWjltj1qNzuXrvzokChsgjGlYxO05MQYxp-PFRfKdqAigJ-t4K4C6ksxfHPwRDfDTumkQ' class='b-hbp-video b-uploaded' FRAMEBORDER='0' />Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com0tag:blogger.com,1999:blog-3905741845175603023.post-61303858976824005472007-11-23T18:53:00.000-08:002007-11-23T19:11:54.424-08:00Special: Turkey Bowl 2007<iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dz3lOptRIgVoLZcJp4JzqA9nldo43qK9HL014SysLWkFULfeW3SY3c2DK6EVbU4EbmcGWl8o1jrtfL7-1caTw' class='b-hbp-video b-uploaded' FRAMEBORDER='0' />Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com1tag:blogger.com,1999:blog-3905741845175603023.post-70942806530034860592007-11-22T15:55:00.000-08:002007-11-22T16:38:43.221-08:00Chapter 7: Alonzo The Fist(Happy Thanksgiving!)<br /><br /><iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dwD45oyzUHGPPrkVd8-r5UlB16_yWjn5DUvajwW_NpQlh8fF_110NGSsPm4xKEzaUuY6bpZ_HUDBAhs6cYaHQ' class='b-hbp-video b-uploaded' FRAMEBORDER='0' />Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com0tag:blogger.com,1999:blog-3905741845175603023.post-73119352681500966442007-11-15T07:37:00.000-08:002007-11-15T17:54:22.558-08:00Chapter 6: Separation<iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dzEjfEmgZWBfWkWHWJHRT_1RUmQiWP2ho0zboG5WDIsQhhccsQMN_cmQUrmfsWTbsVY1wCe_wXBGUjDY41Zzw' class='b-hbp-video b-uploaded' FRAMEBORDER='0' />Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com0tag:blogger.com,1999:blog-3905741845175603023.post-61115399604136142172007-11-13T00:06:00.000-08:002007-11-15T07:37:10.404-08:00Chapter 5: What's in a Name?<iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dyALG7yoDxJ5loRqrKOI6EUgjnMxxEkX0jtkmi1jf_NGQdSpG4fdC7P6NmM_2rK3JRa4T8Xs03rkYc7A3DALg' class='b-hbp-video b-uploaded' FRAMEBORDER='0' />Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com0tag:blogger.com,1999:blog-3905741845175603023.post-79443865606624753742007-11-01T17:04:00.000-07:002007-11-27T10:33:07.319-08:00Chapter 4: Success!<iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dz_-zcPj94qXbSRfG8cY-c8uDfYBGiCJ4o3pwOvd2WKl-yLHJxAnCY543QAl6sNTsQxfRg14ogRbdgvZjtLPg' class='b-hbp-video b-uploaded' FRAMEBORDER='0' />Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com0tag:blogger.com,1999:blog-3905741845175603023.post-88366528659469257432007-10-31T21:00:00.000-07:002007-10-31T21:25:51.554-07:00Documetary: Humble Beginings<p>Chapter Three, where the idea all started.</p><p><iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dz3YGwcLrT0lZhtrQQKaQF1leYvcpcJr-lVdYCaTFoMjAHQtwAjc7vp9cE4HViAWIJtHv32cps4AGnpsx-M7Q' class='b-hbp-video b-uploaded' FRAMEBORDER='0' /></p>Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com0tag:blogger.com,1999:blog-3905741845175603023.post-34910670706831593692007-10-30T17:40:00.000-07:002007-10-30T17:54:09.994-07:00Documentary: The Four<p>This is the second chapter of the documentary. Here, we introduce the four actors who portray the 4 Fighting Serpents of Death, and little more about who they really are. </p><p><iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dwZskTs6WHZnO-dJAY3nCnSE51EPhr-R69YKEe8mzfAvtCqGStq9yYFtr6bbGxZ2Ffu0PFidYQge08vqG4Xlw' class='b-hbp-video b-uploaded' FRAMEBORDER='0' /></p>Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com0tag:blogger.com,1999:blog-3905741845175603023.post-33190610334281896542007-10-26T10:45:00.000-07:002007-10-26T12:35:32.562-07:004FSOD: The Documentary - Opening Credits<p>Here it is folks, the official 4 Fighting Serpents of Death Documentary. What you are seeing now are the opening credits. Check back in weekly as we will be posting subsequent chapters. 12 in all I believe!</p><p> </p><p><iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dwaMaRADWrYCcNuaVJ4PUHWyoeoLnc21ohJRcUbg7iOQUtgHNFTEzzphTjpwJMWygE9o1cQNOFGL0pQh9bM5Q' class='b-hbp-video b-uploaded' FRAMEBORDER='0' /></p>Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com1tag:blogger.com,1999:blog-3905741845175603023.post-7114995983053335962007-10-21T20:22:00.000-07:002007-10-21T22:33:09.329-07:004FSOD: The Documentary!In the footsteps of VH1 Behind the Music, E! True Hollywood Story, and Spinal Tap, the 4 Fighting Serpents of Death have collaborated with Oregon Public Access Television to release their own behind-the-scenes documentary. This complete segment features candid interviews from all of the Serpents as well as anyone ever involved with their films. We will be posting this documentary up in segments, once a week for the next 3 months. It is exciting I know. But before we get started, let's see some trailers for the Documentary!<br /><br /><br /><br /><p><iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dzcR92LiAsXqsOFoG-hjUEen2MLTqNqXNbeNUATPebQLZ1yKQ5dsI-BRbFehbYYFuD-G8kx-B75mD9w02bKhg' class='b-hbp-video b-uploaded' FRAMEBORDER='0' /></p><p> </p><p> </p><iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dygJfitUZmDjWaPuCe1iyEGueFWRSVQtjfEUD2w-NjGXdPdBzlk-GytF3VSg1rYcAqeTjR2zSPxHRRFGq38ow' class='b-hbp-video b-uploaded' FRAMEBORDER='0' />Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com0tag:blogger.com,1999:blog-3905741845175603023.post-13676006727225311882007-10-21T00:06:00.000-07:002007-10-21T00:43:00.495-07:004FSOD: Action Impending!<p>In 2001, the 4FSOD released "Action Impending". You might notice that the acronym for the film is A.I., this was no accident. Knowing that they're popularity had taken a downturn, the Serpents hoped to show up on internet searches by having the same acronym as Stephen Spielberg's film "A.I." which was wildly successful that year. To a lesser degree, the Serpents wanted to show up in Yahoo searches for Allen "The Answer" Iverson too, hoping to attract the younger male demographic. Really, there was no coherent plot to this film. Dick Johnson is strolling with his girlfriend through a parking garage when they happen to come across a battle between Lee Bang (formerly known as 100% BEEF) and Wang Chung and thier nemesis, Alonzo the Fist. Dick Johnson's girl is killed by a flying bamboo stick and he vows revenge. The Serpents now have one more ally in their campaign to take down Alonzo!</p><p><iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dzyGsZvqadWDiHuePjXi1heKyYyyPti7puxO64WdRTvfsDLyNzjsZefN91kpz6t2ezJFggcqbnhL4kjVh_Vaw' class='b-hbp-video b-uploaded' FRAMEBORDER='0' /></p>Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com0tag:blogger.com,1999:blog-3905741845175603023.post-37763903805929167462007-10-12T17:26:00.000-07:002007-10-12T17:55:56.776-07:004FSOD: Critical Review<iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dz-uqLhP2VHS0eK5WJMLHAW4pxevce2gBNo_KusJViQfGeEvRJfNh9UG5Uk4xtLduZC6UeEpxmze7CKKCjYig' class='b-hbp-video b-uploaded' FRAMEBORDER='0' />Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com0tag:blogger.com,1999:blog-3905741845175603023.post-60072994970418036702007-10-03T08:59:00.000-07:002007-10-03T09:22:49.587-07:004FSOD: Deadly WatersDeadly Waters was not released in theaters. Rather, it was released as a Made-For-DVD special. Wang Chung comments, "It would have cost us way too much to produce this one for the big screen. Also, the action we do have looks <span class="blsp-spelling-error" id="SPELLING_ERROR_0">alot</span> more impressive when it's crammed into the small screen." After the non-success of <span class="blsp-spelling-error" id="SPELLING_ERROR_1">Paresuture</span> the year before, many endorsers backed out of the 4<span class="blsp-spelling-error" id="SPELLING_ERROR_2">FSOD</span> campaign, leaving their finances dwindled. Deadly Waters was their next attempt to get back into the spotlight.<br /><br />Plot Synopsis:<br />Wang Chung decides to go to the Oregon Coast for a weekend getaway. All the other Serpents are busy with jobs and other commitments so he calls up his old college <span class="blsp-spelling-error" id="SPELLING_ERROR_3">roomate</span>, Kevin. While kicking it at <span class="blsp-spelling-error" id="SPELLING_ERROR_4">Rockaway</span> Beach, Kevin is ambushed by none other than Alonzo the Fist! This time it's personal! Alonzo also steals Kevin's rented Chevy Cavalier. Wang Chung manages to hunt Alonzo down to the rocky beach. With nowhere else for either of them to go, they must now fight.<br /><br /><br /><p><iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dySRctwDWWPjKF_lYL5Ou0G2ApDJd0FHy8817hhc_FPha9EknOgitHUOFMLSLpBqtFssQgbZlmMGL6BzJDnow' class='b-hbp-video b-uploaded' FRAMEBORDER='0' /></p><p></p><p>*4<span class="blsp-spelling-error" id="SPELLING_ERROR_5">FSOD</span>*</p>Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com0tag:blogger.com,1999:blog-3905741845175603023.post-84560047708297999962007-10-01T21:55:00.000-07:002007-10-01T22:20:14.881-07:004FSOD: Search for AlonzoThis emotionally charged installment of the 4FSOD is sure to be a rollercoaster. We now reveal some of Alonzo's past. Alonzo the Fist was not always a bad dude. He once had a life, he once had a family, and he once had a sister. Her name was Sophia Marteaux, the "Soft Hammer". Some scholars suspect that Alonzo's trip down evil lane began when he was kidnapped by the government and forced to be the guinea pig for a number of human weapon experiments. They thought no one would notice Alonzo missing, but they were wrong. Sophia Marteaux noticed, and now she wants to get her brother back.<br /><br />The Search for Alonzo<br /><p><iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dwFYsxOIN76KChzyh46wWrb9kAUVYkXM6s6kiZhjleKZ_Yd7pE1Y2iIyo7cYPik1gQ7VBZPsRQJN-CJU7c5nw' class='b-hbp-video b-uploaded' FRAMEBORDER='0' /></p><p> </p><p>*4FSOD*</p>Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com0tag:blogger.com,1999:blog-3905741845175603023.post-29862062712274548652007-10-01T14:10:00.001-07:002007-10-01T14:26:15.951-07:004FSOD: ParesutureThe film "Paresuture" was probably the Serpent's least successful film. While filled with promise initially, box office numbers proved that the film was a flop. Wang Chung defends, "You know, it was actually a little abstract compared to the previous films. But we wanted to keep things fresh, that's why we did it. Ya live ya learn I guess."<br />In Paresuture, Alonzo the Fist purchases a time machine through Craigslist and intends to go the future to learn the next 10 superbowl winners. Then return to the present and bet on them. Wang Chung and his partner, 100% Beef buy a time-machine of their own but use the wrong crystals! Consequently, they get catapulted into the past. It's one big chronological mess that needs to be sorted out, or else!<br />The Past, The Present, and The Future, blur together to make... Paresuture!<br /><br /><br /><p><iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dwISi0-BLlWw2AXM0_FWBiv2JYtgKqTXM0caqQ2kE7Vklww5W409kgfMtmzsrlGM2wmenFMjcvuajGsXaIDlQ' class='b-hbp-video b-uploaded' FRAMEBORDER='0' /></p><p>*4FSOD*</p>Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com0tag:blogger.com,1999:blog-3905741845175603023.post-83939852294338951462007-09-30T16:39:00.000-07:002007-09-30T20:00:48.192-07:004FSOD: The Nemesis and The ConfrontationAlonzo The Fist:<br />Every hero needs a villian, like every rose has it's thorn. In general, the 4FSOD clean up on most of the competition. But then they met Alonzo The Fist. No one really knows where he came from and his lineage is the stuff of scary campfire stories. One thing is for certain though, Alonzo The Fist really hates the Four Fighting Serpents of Death.<br /><br /><iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dz0Rove_XTtpCbTLlPqCxYWklr4JyMLWqeasU5E7KWU1I-r70TH8CYdCSG1QLPllrlTeH8PUaiwkLiz9jFdtg' class='b-hbp-video b-uploaded' FRAMEBORDER='0' /><br /><br /><br /><br /><br /><p> </p><p></p><p>*4FSoD*</p>Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com0tag:blogger.com,1999:blog-3905741845175603023.post-62592224328992184292007-09-30T16:00:00.000-07:002007-09-30T20:25:34.450-07:004FSOD: Vengence CompoundVengence Compound:<br />In this movie right here, we get to see one of the most intense fight sequences ever caught tape. Alonzo The Fist has managed to captured three of the four Serpents and locked them in a room that blasts nothing but Trio's hit single, "Da Da Da" on repeat. A horrible torture indeed. Alonzo plans to finish of the extermination and hunts down the remaining Serpent, Wang Chung who was picking apples in a nearby orchard at the time. But it wasn't going to be that easy. Being a resilient tiger of action, Wang Chung battles hard, not only to save his own life but the lives of his friends! Could this be 4FSOD's last stand? Watch and find out!<br /><br /><br /><iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dx9OzVrg6E1GmsZH3bzwai_uPqwDKDz6-i43epWK4eUg5B0rSVgdAuFYOQtRKBA1pC5bRpyAOGbJdCTpeWflg' class='b-hbp-video b-uploaded' FRAMEBORDER='0' />Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com0tag:blogger.com,1999:blog-3905741845175603023.post-82352286232836488352007-09-28T10:38:00.001-07:002007-09-28T10:56:18.792-07:004FSOD: Who is Stir Frey Jones?Who are the four fighting serpents? Are there really 4 of them? We'll answer that later. For now, you only need to know of one Serpent of Death and his name is Stir Frey Jones. In this action packed film, a couple hoodlums steal a woman's purse in a parking garage. The nerve!<br />Little did they now, a Serpent was watching in wings. Exhibiting a merciless tenacity against evil, Stir Frey Jones tears things up all in the name of justice. The only question now is who will spare mercy on these bad guys?<br /><br /><br /><p><iframe allowFullScreen='true' webkitallowfullscreen='true' mozallowfullscreen='true' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dxRJJzvi9UBzAIbN6yiv3-7O1NWvXGQ6mtedeJwfe-G44tldl5YtLtl7tomHMFPvy9xu4Hn3wkuTnU7wXLTqw' class='b-hbp-video b-uploaded' FRAMEBORDER='0' /></p><p> </p><p>*4FSoD*</p>Anthonyhttp://www.blogger.com/profile/05977683271610009040noreply@blogger.com0 \ No newline at end of file diff --git a/RSParser/Tests/RSParserTests/Resources/DaringFireball.atom b/RSParser/Tests/RSParserTests/Resources/DaringFireball.atom deleted file mode 100644 index 4d84dadbd..000000000 --- a/RSParser/Tests/RSParserTests/Resources/DaringFireball.atom +++ /dev/null @@ -1,1821 +0,0 @@ - - -Daring Fireball -By John Gruber - - -https://daringfireball.net/feeds/main - - -2017-06-27T00:54:20ZCopyright © 2017, John Gruber - The Talk Show: ‘I Do Like Throwing a Baby’ - - - - tag:daringfireball.net,2017:/linked//6.33853 - 2017-06-27T00:54:17Z - 2017-06-27T00:54:20Z - - John Gruber - http://daringfireball.net/ - - New episode of America’s favorite 3-star podcast, with special guest John Moltz. Topics include more follow-up from WWDC 2017, the iPad Pro models and ProMotion, Scott Forstall’s interview with John Markoff regarding the 10-year anniversary of the original iPhone, the ongoing shitshow at Uber, quick thoughts on the Nintendo Switch, and more. Also: guess which John enjoys throwing babies into the air.

- -

Sponsored by:

- -
    -
  • Squarespace: Make your next move. Use code GRUBER for 10% off your first order.
  • -
  • Away: High-quality luggage with built-in USB chargers. Save $20 with promo code TALKSHOW.
  • -
  • MailRoute: Hosted spam and virus protection for email. Use this link for 10% off for the life of your account.
  • -
- - - - ]]>
-
- Virgin Mobile Partners With Apple to Go iPhone-Only With $1 Service - - - - tag:daringfireball.net,2017:/linked//6.33852 - 2017-06-26T23:51:11Z - 2017-06-26T23:51:13Z - - John Gruber - http://daringfireball.net/ - - Josh Centers, writing for TidBITS on Virgin Mobile’s intriguing decision to go iPhone-only:

- -
-

Pundits have long suspected that two roadblocks stood in the way -of Apple becoming a carrier: the infrastructure is incredibly -expensive, even if you lease it from the larger carriers, and -Apple could limit the iPhone business if it were to compete with -the major carriers.

- -

But Apple has sidestepped those concerns by essentially taking -over a carrier (actually a carrier-owned MVNO — Mobile Virtual -Network Operator) without acquiring it. Apple may not own Virgin -Mobile, but Virgin Mobile is now utterly dependent on Apple and -will benefit through promotion in Apple Stores.

- -

We shouldn’t read too much into this deal, but at the very least -it’s unusual to see a company like Virgin Mobile going all-in on -the iPhone. And it might point toward Apple dipping its toe into -the MVNO business.

-
- -

Virgin Mobile is owned by Sprint (and thus uses Sprint’s back-end), and in my experience Sprint is the worst of the U.S. carriers, so this is not a panacea. But it is intriguing.

- - - - ]]>
-
- The Verge: ‘Apple’s AR Is Closer to Reality Than Google’s’ - - - - tag:daringfireball.net,2017:/linked//6.33850 - 2017-06-26T23:02:21Z - 2017-06-26T23:02:23Z - - John Gruber - http://daringfireball.net/ - - Two great examples via the very fun Made With ARKit Twitter account: here and here.

- - - - ]]>
-
- Rene Ritchie’s First Look at the iOS 11 Public Beta - - - - tag:daringfireball.net,2017:/linked//6.33849 - 2017-06-26T22:55:13Z - 2017-06-26T23:13:17Z - - John Gruber - http://daringfireball.net/ - - Rene Ritchie has a comprehensive look at the just-released public beta of iOS 11. Romain Dillet has a good preview at TechCrunch too. The gist of both previews: it’s the “I hope Apple truly focuses on the iPad this year” release of iOS that we’ve been waiting for.

- -

I’ve been using the developer betas on my 10.5-inch iPad Pro review unit and a spare iPhone. I’m willing to wait to install iOS 11 on my primary iPhone, but at this point, bugs be damned, I wouldn’t want to use an iPad running iOS 10.3. It’s stable enough, and the benefits of the great new features for iPad far outweigh the downsides of the beta (which, in addition to crashing bugs, include questionable battery life).

- - - - ]]>
-
- Amazon’s New Echo Show Is Very Cool and a Little Creepy - - - - tag:daringfireball.net,2017:/linked//6.33848 - 2017-06-26T22:08:09Z - 2017-06-26T23:18:49Z - - John Gruber - http://daringfireball.net/ - - Mat Honan, writing for BuzzFeed:

- -
-

It has this wild new feature called Drop In. Drop In lets you give -people permission to automatically connect with your device. -Here’s how it works. Let’s say my father has activated Drop In for -me on his Echo Show. All I have to do is say, “Alexa, drop in on -Dad.” It then turns on the microphone and camera on my father’s -device and starts broadcasting that to me. For the several seconds -of the call, my father’s video screen would appear fogged over. -But then there he’ll be. And to be clear: This happens even if he -doesn’t answer. Unless he declines the call, audibly or by tapping -on the screen, it goes through. It just starts. Hello, you look -nice today.

- -

Honestly, I haven’t figured out what to think about this yet. But, -it’s here.

-
- -

I know what to think of this: No fucking way do I want this.

- -

Update: I’ve already gotten a few reader responses arguing that this feature could be great for an Echo Show in the home of an elderly relative. You visit and set it up in their house, explain to them what it does, and then you can check in with them without their needing to do anything at all. I can see that. You can think of it as the digital equivalent of having a set of keys to someone’s house — something you’d only grant to a deeply trusted friend or loved one.

- - - - ]]>
-
- Verizon to Block Email Addresses From Rival Carriers From Logging Into Yahoo or Tumblr Accounts - - - - tag:daringfireball.net,2017:/linked//6.33847 - 2017-06-26T21:18:46Z - 2017-06-26T22:18:03Z - - John Gruber - http://daringfireball.net/ - - From a Tumblr help document euphemistically titled “Heads-Up for AT&T Customers”:

- -
-

Starting on June 30, 2017, att.net customers will no longer be -able to log in to their Yahoo and Tumblr accounts through email -addresses with the following domains: att.net, ameritech.net, -bellsouth.net, flash.net, nvbell.net, pacbell.net, prodigy.net, -sbcglobal.net, snet.net, swbell.net, and wans.net.

-
- -

The sheer egregiousness of this is outrageous on its face, but it’s even worse when you consider that Tumblr, when it was independent, was a champion for net neutrality.

- -

Update: TechCrunch says it’s just a deal expiring, not spite:

- -
-

As part of the new corporate merger of Yahoo and Aol under the -Oath brand, it looks like Yahoo accounts will no longer be -accessible through AT&T email addresses (or those of any A&T -subsidiaries).

- -

The move provoked some uproar among net neutrality advocates, but -it seems to be less about creating walled gardens and more about -cleaning up prior commitments and pre-existing partnerships. -While there is a level of inconvenience for AT&T customers, this -is less about net neutrality and more about unwinding those -corporate deals.

-
- -

I still say fuck Verizon and their stance on net neutrality.

- - - - ]]>
-
- Trump’s Lies, the Definitive List - - - - tag:daringfireball.net,2017:/linked//6.33846 - 2017-06-26T21:08:43Z - 2017-06-26T21:08:44Z - - John Gruber - http://daringfireball.net/ - - Copiously documented and perfectly presented. Looked striking in the print edition, too.

- - - - ]]>
-
- - - - - tag:daringfireball.net,2017:/feeds/sponsors//11.33851 - Daring Fireball Department of Commerce - 2017-06-26T19:22:44-04:00 - 2017-06-26T19:22:46-04:00 - Time is your most precious resource. You need to know how you are spending it.
-But time tracking sucks. Big Time. (Pun intended.)

- -

The brand new Timing fixes that. It automatically tracks which apps, documents and websites you use — without start/stop timers. See how you spend your time, eliminate distracting activities, and improve your client billing. Mind you, this data is super sensitive, so Timing keeps it safe on your Mac.

- -

Stop worrying about time and focus on doing your best work instead.

- -

Download a free 14-day trial today and get 10 percent off through next Monday.

- - - - ]]>
- [Sponsor] Timing — Automatic Time Tracking for Mac
- Delta Updates and App Thinning Do Not Solve the Apps-Are-Too-Damn-Big Problem - - - - tag:daringfireball.net,2017:/linked//6.33845 - 2017-06-25T15:31:10Z - 2017-06-25T15:31:12Z - - John Gruber - http://daringfireball.net/ - - Matt Birchler:

- -
-

“App thinning” is not a magic bullet that erases this problem -though, as Facebook Messenger, which shows as being 154 MB, still -downloaded 99 MB of data for its update. […]

- -

So are giant app sizes a problem? Yes. Do delta updates allow -these updates to use less data? Yes. Do delta updates make these -large apps a non-issue? Hell no!

-
- - - - ]]>
-
- Mnml – A Mac Client for Medium - - - - tag:daringfireball.net,2017:/linked//6.33844 - 2017-06-25T00:14:25Z - 2017-06-25T02:42:27Z - - John Gruber - http://daringfireball.net/ - - My thanks to Mnml for sponsoring this week’s DF RSS feed. It’s a native Mac app client for Medium, and can be used for writing, blogging, and notetaking. Based upon the same engine that twice won Desk “Best Apps of the Year” honors, Mnml has all the features you’ll need, wrapped in an attractive, fun – and yes, minimal – interface. Anyone who writes for Medium and prefers native client apps should check it out.

- - - - ]]>
-
- Scott Galloway on Amazon and Whole Foods - - - - tag:daringfireball.net,2017:/linked//6.33843 - 2017-06-23T18:45:50Z - 2017-06-23T19:23:34Z - - John Gruber - http://daringfireball.net/ - - Scott Galloway:

- -
-

Amazon / Whole Foods will be the fourth-largest grocer in the US, -and will likely post growth rates no $10B+ retailer, sans Amazon, -has registered. The Seattle firm will apply its operational chops -and lower (zero) profit hurdle to the Whole Foods business model -and bring prices (way) down. If you wish you could shop at Whole -Foods more often, but it’s too expensive, your prayers have been -answered. Whole Foods will become the grocery equivalent of a -Mercedes for the price of a Toyota. Grocery has stuck their chin -out (little innovation), and the entire sector is about to have -its jaw shattered.

-
- -

It’s a great piece. I disagree with him on this though:

- -
-

Amazon will displace Apple as the top tech hardware innovator, -with Alexa cementing itself as the gadget that defines the decade -(post iPhone). Grocery / commerce via Alexa will create the -utility that Alexa needs to [maintain its lead] over Google and -Apple’s home / voice offerings as they try to play catch-up.

-
- -

Alexa may well maintain its lead in the smart speaker market. It may even grow. Maybe HomePod will be a complete bust. But even if all of that happens, the smartphone will remain the dominant device in people’s lives. Something will eventually replace the phone, but smart speakers aren’t it.

- -

Hardware just isn’t where Amazon is good.

- - - - ]]>
-
- Google Will Stop Reading Your Emails for Gmail Ads - - - - tag:daringfireball.net,2017:/linked//6.33842 - 2017-06-23T18:32:03Z - 2017-06-23T18:32:05Z - - John Gruber - http://daringfireball.net/ - - Mark Bergen, reporting for Bloomberg:

- -
-

Google is stopping one of the most controversial advertising -formats: ads inside Gmail that scan users’ email contents. The -decision didn’t come from Google’s ad team, but from its cloud -unit, which is angling to sign up more corporate customers.

- -

Alphabet Inc.’s Google Cloud sells a package of office software, -called G Suite, that competes with market leader Microsoft Corp. -Paying Gmail users never received the email-scanning ads like the -free version of the program, but some business customers were -confused by the distinction and its privacy implications, said -Diane Greene, Google’s senior vice president of cloud. “What we’re -going to do is make it unambiguous,” she said.

-
- -

This is terrific news. Not just because it’s a good policy change in and of itself, but I take it as a sign that Google’s leadership is starting to realize how much damage they’ve done to the company’s reputation by playing fast and loose with their users’ privacy.

- - - - ]]>
-
- Who Americans Spend Their Time With - - - - tag:daringfireball.net,2017:/linked//6.33841 - 2017-06-23T18:13:55Z - 2017-06-23T18:15:59Z - - John Gruber - http://daringfireball.net/ - - Via Jim Coudal, who summarizes this perfectly: “Poetry, in data”.

- - - - ]]>
-
- Kevin Drum’s Thoughtful Critique of Trumpcare - - - - tag:daringfireball.net,2017:/linked//6.33840 - 2017-06-23T17:58:25Z - 2017-06-23T17:58:27Z - - John Gruber - http://daringfireball.net/ - - A succinct rundown of what’s wrong with the Senate Republicans’ “health care” bill.

- - - - ]]>
-
- Nintendo: Switch Shortages Are ‘Definitely Not Intentional’ - - - - tag:daringfireball.net,2017:/linked//6.33839 - 2017-06-23T17:46:13Z - 2017-06-23T17:46:15Z - - John Gruber - http://daringfireball.net/ - - Kyle Orland, reporting for Ars Technica:

- -
-

Since the days of the NES, people have accused Nintendo of -intentionally underproducing hardware in order to drive an -artificial feeding frenzy of demand in the marketplace. With the -Nintendo Switch remaining nearly impossible to find at retailers -nationwide, those same accusations of “false scarcity” have been -bubbling up in certain corners.

- -

Nintendo Senior Director of Corporate Communications Charlie -Scibetta wants to push back on those accusations. “It’s definitely -not intentional in terms of shorting the market,” he told Ars in a -recent interview. “We’re making it as fast as we can. We want to -get as many units out as we can to support all the software that’s -coming out right now… our job really is to get it out as quick -as we can, especially for this holiday because we want to have -units on shelves to support Super Mario Odyssey.”

-
- - - - ]]>
-
- How to Build Smaller Apps - - - - tag:daringfireball.net,2017:/linked//6.33838 - 2017-06-22T23:03:38Z - 2017-06-22T23:03:40Z - - John Gruber - http://daringfireball.net/ - - Ben Sandofsky:

- -
-

Popular social networking apps are over 400 megs. With weekly -releases, over one year you’ll download twenty gigs of data.

- -

Since we launched Halide, the most unexpected compliment we’ve -heard is about its size. At 11 megs, we’ll push less data in one -year than a social network pushes in a single update.

- -

“So you aren’t using Swift,” asked a friend. After all, Swift -bundles its standard libraries into your app, bloating its size. -Halide is almost entirely Swift. How did we do it? Let’s start -with the technical bits.

-
- -

His conclusion is spot-on:

- -
-

There really is one weird trick to lose size: focus on your customers.

-
- - - - ]]>
-
- Bad App Citizens - - - - tag:daringfireball.net,2017:/linked//6.33837 - 2017-06-22T22:51:51Z - 2017-06-23T16:54:33Z - - John Gruber - http://daringfireball.net/ - - Jon Darke:

- -
-

This got me thinking — as a user who has a lot of apps -installed, how much bandwidth does my phone use to keep my apps -updated? […]

- -

One Friday I turned off auto-update for apps and let the update -queue build up for a week. The results shocked me.

-
- -

It’s getting to the point where most apps can’t be updated over cellular because they’re all over 100 MB. This is madness.

- -

Update: Many readers have written to argue that the listed sizes in the App Store aren’t what you actually download when updating an app, thanks to app thinning and other features. OK, but even with app thinning and delta updates these apps are still way too big as downloads and take up way too much storage on devices.

- - - - ]]>
-
- More Than 1,000 Uber Employees Ask for Travis Kalanick to Return - - - - tag:daringfireball.net,2017:/linked//6.33836 - 2017-06-22T22:24:24Z - 2017-06-22T22:24:26Z - - John Gruber - http://daringfireball.net/ - - Dan Primack, reporting for Axios:

- -
-

More than one thousand current Uber employees have signed a letter -to the company’s board of directors, asking for the return of -deposed CEO Travis Kalanick “in an operational role.” One of its -venture capital investors also is chiming in, with a similar -message.

-
- -

Not surprising to me at all — Uber was made in Kalanick’s image.

- - - - ]]>
-
- Kara Swisher: ‘Susan Fowler Proved That One Person Can Make a Difference’ - - - - tag:daringfireball.net,2017:/linked//6.33835 - 2017-06-22T20:15:16Z - 2017-06-22T20:15:18Z - - John Gruber - http://daringfireball.net/ - - Kara Swisher:

- -
-

It was Lao Tzu who said that “the journey of a thousand miles -begins with a single step.”

- -

In the case of complete and utter change reeling through Uber -right now — culminating in the resignation of its once -untouchable CEO Travis Kalanick — it turns out that it began with -one of the most epic blog posts to be written about what happens -when a hot company becomes hostage to its increasingly -dysfunctional and toxic behaviors.

- -

It was clear from the moment you read the 3,000-word post by -former engineer Susan Fowler about her time at the car-hailing -company that nothing was going to be the same. Titled simply, -“Reflecting on one very, very strange year at Uber,” the -essay deftly and surgically laid out the map that the media and -others would use to prove to its out-to-lunch board and waffling -investors that Uber CEO Travis Kalanick had to go.

-
- -

The truth and courage are a powerful combination.

- - - - ]]>
-
- Gizmodo Investigation Exposes Websites Collecting Form Data Before You Hit ‘Submit’ - - - - tag:daringfireball.net,2017:/linked//6.33834 - 2017-06-22T19:54:12Z - 2017-06-22T23:18:05Z - - John Gruber - http://daringfireball.net/ - - Great investigative work by Kashmir Hill and Surya Mattu for Gizmodo:

- -
-

During a recent investigation into how a drug-trial recruitment -company called Acurian Health tracks down people who look online -for information about their medical conditions, we discovered -NaviStone’s code on sites run by Acurian, Quicken Loans, a -continuing education center, a clothing store for plus-sized -women, and a host of other retailers. Using Javascript, those -sites were transmitting information from people as soon as they -typed or auto-filled it into an online form. That way, the company -would have it even if those people immediately changed their minds -and closed the page. […]

- -

We decided to test how the code works by pretending to shop on -sites that use it and then browsing away without finalizing the -purchase. Three sites — hardware site Rockler.com, gift site -CollectionsEtc.com, and clothing site BostonProper.com — sent us -emails about items we’d left in our shopping carts using the email -addresses we’d typed onto the site but had not formally submitted. -Although Gizmodo was able to see the email address information -being sent to Navistone, the company said that it was not -responsible for those emails.

-
- -

They weren’t responsible for sending the emails, but they were responsible for the email addresses being sent to those websites in the first place. Sending form data surreptitiously is morally wrong, and everyone knows it.

- -

This might sound hyperbolic, but I mean it: I think we’d be better off if JavaScript had never been added to web browsers.

- - - - ]]>
-
- Uber’s Biggest Problem: Its Business Model - - - - tag:daringfireball.net,2017:/linked//6.33833 - 2017-06-22T18:54:45Z - 2017-06-22T18:54:46Z - - John Gruber - http://daringfireball.net/ - - Christopher Mims, in his column for The Wall Street Journal:

- -
-

But even when it steers through that thicket of crises, Uber will -have to come to grips with a fundamental vulnerability that is -increasingly apparent in the company’s business model. Uber may be -great at technology, but unlike the businesses of Google, -Facebook, Apple or Amazon, technology hasn’t proven to be a -significant barrier to new entrants in ride-sharing. Across the -globe, Uber has dozens of competitors, and in many markets they -have grabbed the lion’s share of the ride-sharing market.

- -

Even if Uber fixes all of its current problems, it’s increasingly -unlikely that it can live up to the inflated expectations that -come with the nearly $70 billion valuation that have made it the -world’s most valuable startup. There are barbarians at Uber’s -gate, and it’s sorely in need of a moat.

-
- -

This is why they’re pursuing self-driving technology so aggressively. There’s simply no way that Uber is worth $70 billion without some sort of exclusive technical advantage. That’s the interesting flip side to Kalanick’s ouster — I’m not sure who would want the job.

- - - - ]]>
-
- Chris Lattner on His Stint at Tesla - - - - tag:daringfireball.net,2017:/linked//6.33832 - 2017-06-22T18:26:01Z - 2017-06-22T19:13:09Z - - John Gruber - http://daringfireball.net/ - - Chris Lattner has updated his resume with his accomplishments at Tesla. Unsurprisingly, it sounds like he got a lot done in just five months — including, ironically, addressing an engineering talent retention problem.

- - - - ]]>
-
- Inside Travis Kalanick’s Resignation - - - - tag:daringfireball.net,2017:/linked//6.33831 - 2017-06-22T18:07:14Z - 2017-06-22T18:07:15Z - - John Gruber - http://daringfireball.net/ - - Mike Isaac, reporting for The New York Times:

- -
-

Travis Kalanick’s final hours as Uber’s chief executive played out -in a private room in a downtown Chicago hotel on Tuesday.

- -

There, Mr. Kalanick, who was on a trip to interview executive -candidates for Uber, was paid a surprise visit. Two venture -capitalists — Matt Cohler and Peter Fenton of the Silicon Valley -firm Benchmark, which is one of Uber’s biggest shareholders — -presented Mr. Kalanick with a list of demands, including his -resignation before the end of the day. The letter was from five of -Uber’s major investors, including Benchmark and the mutual fund -giant Fidelity Investments. […]

- -

By the end of the day, after hours of haggling and arguing, that -course was clear: Mr. Kalanick agreed to step down as Uber’s chief -executive.

-
- -

Truly great reporting from Isaac, including the fact that even during his brief “leave of absence”, he wasn’t really absent at all:

- -
-

In reality, Mr. Kalanick had little intention of staying away from -his company. Almost immediately after announcing the leave of -absence, he worked the phones to push out Mr. Bonderman for making -the sexist comment onstage at an Uber employee meeting. With the -two increasingly at odds, Mr. Kalanick sent out a flurry of texts, -phone calls and emails to his allies to pressure Mr. Bonderman to -step down from Uber’s board. Hours later, Mr. Bonderman did.

-
- - - - ]]>
-
- Narrative Maps for ‘Choose Your Own Adventure’ Books - - - - tag:daringfireball.net,2017:/linked//6.33830 - 2017-06-22T17:50:49Z - 2017-06-22T17:59:41Z - - John Gruber - http://daringfireball.net/ - - Sarah Laskow, writing for Atlas Obscura:

- -
-

The last installment of the original “Choose Your Own Adventure” -series came out in 1998, but since 2004, Chooseco, founded by -one of the series’ original authors, R.A. Montgomery, has been -republishing classic volumes, as well as new riffs on the form of -interactive fiction that seemed ubiquitous in the 1980s and ’90s. -The new editions also carry an additional feature — maps of the -hidden structure of each book.

-
- -

Just looking at the maps included in the article, it’s interesting how wildly varying in complexity these stories were. See also: Christian Swinehart’s color-coded graphical representations of these books.

- -

(Via Kottke.)

- - - - ]]>
-
- John Markoff’s Interview With Scott Forstall and Members of the Original iPhone Team - - - - tag:daringfireball.net,2017:/linked//6.33829 - 2017-06-22T17:45:20Z - 2017-06-22T20:17:34Z - - John Gruber - http://daringfireball.net/ - - The Computer History Museum (now on YouTube):

- -
-

Museum Historian John Markoff moderates a discussion with former -iPhone team members Hugo Fiennes, Nitin Ganatra and Scott Herz, -followed by a conversation with Scott Forstall.

-
- -

Fascinating stories.

- -

Forstall was great. It’s hard to believe he’s been out of Apple and out of the limelight for 5 years — watching him on stage with Markoff it feels like he never left.

- - - - ]]>
-
- ‘Four Things in Those Two Sentences’ - - - - tag:daringfireball.net,2017:/linked//6.33828 - 2017-06-21T16:55:23Z - 2017-06-21T16:55:26Z - - John Gruber - http://daringfireball.net/ - - Kara Swisher on Travis Kalanick:

- -
-

Uber confirmed the resignation, and the company’s board issued a -statement that said, in part: “Travis has always put Uber first. -This is a bold decision and a sign of his devotion and love for -Uber.” (For those who don’t speak fluent tech director, there are -four things in those two sentences that are not true.)

-
- - - - ]]>
-
- Uber Founder Travis Kalanick Resigns as C.E.O. - - - - tag:daringfireball.net,2017:/linked//6.33827 - 2017-06-21T16:53:04Z - 2017-06-21T16:53:06Z - - John Gruber - http://daringfireball.net/ - - Mike Isaac, reporting for The New York Times:

- -
-

In the letter, titled “Moving Uber Forward” and obtained by The -New York Times, the investors wrote to Mr. Kalanick that he must -immediately leave and that the company needed a change in -leadership. Mr. Kalanick, 40, consulted with at least one Uber -board member, and after long discussions with some of the -investors, he agreed to step down. He will remain on Uber’s board -of directors.

- -

“I love Uber more than anything in the world and at this difficult -moment in my personal life I have accepted the investors request -to step aside so that Uber can go back to building rather than be -distracted with another fight,” Mr. Kalanick said in a statement.

-
- -

From the outside, it seems like this was inevitable. It was only a question of when.

- - - - ]]>
-
- Chris Lattner Out at Tesla - - - - tag:daringfireball.net,2017:/linked//6.33825 - 2017-06-21T01:52:14Z - 2017-06-21T01:54:56Z - - John Gruber - http://daringfireball.net/ - - Chris Lattner:

- -
-

Turns out that Tesla isn’t a good fit for me after all. I’m interested to hear about interesting roles for a seasoned engineering leader!

-
- -

That was quick — he was only hired 5 months ago.

- - - - ]]>
-
- - - - - tag:daringfireball.net,2017:/feeds/sponsors//11.33826 - Daring Fireball Department of Commerce - 2017-06-20T23:12:26-04:00 - 2017-06-24T20:23:18-04:00 - Created for discerning Medium.com writers and publishers, it’s the first dedicated desktop publishing client on the Mac App Store. Featured Worldwide on release, it’s the last writing, blogging, and note-taking app you’ll need.

- -

Functional and fun yet mnml af. 🤔 😆 🔥

- - - - ]]>
- [Sponsor] MNML: A macOS Writing App for Medium.com
- The Outline: ‘Inside Apple’s Global War on Leakers’ - - - - tag:daringfireball.net,2017:/linked//6.33824 - 2017-06-20T18:22:34Z - 2017-06-20T18:22:37Z - - John Gruber - http://daringfireball.net/ - - William Turton has quite a scoop for The Outline:

- -
-

A recording of an internal briefing at Apple earlier this month -obtained by The Outline sheds new light on how far the most -valuable company in the world will go to prevent leaks about new -products. […]

- -

The briefing, which offers a revealing window into the company’s -obsession with secrecy, was the first of many Apple is planning to -host for employees. In it, Rice and Freedman speak candidly about -Apple’s efforts to prevent leaks, discuss how previous leakers got -caught, and take questions from the approximately 100 attendees.

-
- -

There’s some irony in a leaked recording of an internal briefing on stopping leaks.

- -

This is news to me:

- -
-

However, Rice says, Apple has cracked down on leaks from its -factories so successfully that more breaches are now happening on -Apple’s campuses in California than its factories abroad. “Last -year was the first year that Apple [campuses] leaked more than the -supply chain,” Rice tells the room. “More stuff came out of Apple -[campuses] last year than all of our supply chain combined.” […]

- -

In the years since Tim Cook pledged to double down on secrecy, -Rice’s team has gotten better at safeguarding enclosures. “In 2014 -we had 387 enclosures stolen,” he says. “In 2015 we had 57 -enclosures stolen, 50 of which were stolen on the night of -announce, which was so painful.” In 2016, Rice says the company -produced 65 million housings, and only four were stolen. “So it’s -about a one in 16 million loss ratio, which is unheard of in the -industry.”

-
- -

There’s a short (15 minute) podcast that accompanies the report, with Turton and The Outline’s Adrianne Jeffries. It’s worth a listen. (It doesn’t seem possible to link directly to a single episode of their podcast, so here’s a direct link for Overcast users.)

- - - - ]]>
-
- The Talk Show: ‘Egg Freckles’ - - - - tag:daringfireball.net,2017:/linked//6.33823 - 2017-06-20T02:02:46Z - 2017-06-20T02:02:48Z - - John Gruber - http://daringfireball.net/ - - New episode of my podcast, The Talk Show, with special guest Serenity Caldwell. We look back at WWDC 2017 — iOS 11, the new iPad Pro models, MacOS 10.13 “High Sierra”, updated Mac hardware and a tease at the upcoming iMac Pro, where Apple might go with VR and AR, San Jose as the venue for the event itself, and more.

- -

Sponsored by:

- -
    -
  • Warby Parker: Boutique-quality, vintage-inspired glasses at a revolutionary price. Try up to five pairs at home for free.
  • -
  • Squarespace: Make your next move. Make your next website. Use code gruber for 10% off your first order.
  • -
  • Fracture: Your photos, printed beautifully directly on glass.
  • -
- - - - ]]>
-
- - - - tag:daringfireball.net,2017://1.33822 - 2017-06-20T00:22:59Z - 2017-06-20T20:08:46Z - - John Gruber - http://daringfireball.net/ - - When people click a URL and see that it’s a Medium site, their reaction should be “Oh, good, a Medium site — this will be nice to read.” Right now it’s gotten to the point where when people realize an article is on Medium, they think, “Oh, crap, it’s on Medium.” - Medium seems to continue to grow in popularity as a publishing platform, and as it does, I’m growing more and more frustrated by their on-screen “engagement” turds. Every Medium site displays an on-screen “sharing” bar that covers the actual content I want to read. This is particularly annoying on the phone, where screen real estate is most precious. Now on iOS they’ve added an “Open in App” button that literally makes the last 1-2 lines of content on screen unreadable. To me these things are as distracting as having someone wave their hand in front of my face while I try to read.

- -

Here’s an annotated screenshot (and threaded rant) I posted to Twitter while trying to read Steven Sinofksy’s WWDC 2017 trip report on my iPad Pro review unit last week.

- -

Safari already has a built-in Sharing button. It has all the options for sharing I need. And as I scroll the page, it disappears so that I can see as much text on screen as possible. Safari is designed to be reader-friendly, as it should be. But it’s trivial to get that Sharing button back when I want it – just tap the bottom of the screen and there it is. Easy.

- -

This is now a very common design pattern for mobile web layouts. Medium is far from alone. It’s getting hard to find a news site that doesn’t put a persistent sharing dickbar down there.

- -

More examples:

- - - -

TechCrunch’s waste of space deserves special mention, for having a persistent navbar at the top and a persistent ad, in addition to their sharing dickbar.

- -

I’m sure “engagement” does register higher with these sharing dickbars, but I suspect a big part of that is because of accidental taps. And even so, what is more important, readability or “engagement”? Medium wants to be about readability but that’s hard to square with this dickbar, and especially hard to square with the “Open in App” button floating above it.

- -

iOS also has a standard way to prompt users to install the app version of a website — Smart App Banners. And it’s user-dismissible.

- -

For any piece over a page long, I read Medium pieces with Safari’s Reader Mode. Medium is supposed to be a reader-optimized layout by default. It should be one of the sites where you’re never even tempted to switch to Reader Mode.

- -

I’m frustrated by this design pattern everywhere I see it. But I’m especially disappointed by Medium’s adoption of it. I don’t expect better from most websites. I do expect better from Medium.

- -

A website should not fight the browser. Let the browser provide the chrome, and simply provide the content. Web developers know this is right — these dickbars are being rammed down their throats by SEO experts. The SEO folks are the same dopes who came up with the genius strategy of requiring 5-10 megabytes of privacy-intrusive CPU-intensive JavaScript on every page load that slows down websites. Now they come to their teams and say, “Our pages are too slow — we gotta move to AMP so our pages load fast.”

- -

I don’t expect to break through to the SEO shitheads running the asylums at most of these publications, but Medium is supposed to be good. When people click a URL and see that it’s a Medium site, their reaction should be “Oh, good, a Medium site — this will be nice to read.” Right now it’s gotten to the point where when people realize an article is on Medium, they think, “Oh, crap, it’s on Medium.”

- - - - ]]>
- ★ Medium and the Scourge of Persistent Sharing Dickbars
- Microsoft Surface Laptop Teardown - - - - tag:daringfireball.net,2017:/linked//6.33821 - 2017-06-19T19:43:57Z - 2017-06-19T20:07:54Z - - John Gruber - http://daringfireball.net/ - - iFixit gave the Surface Laptop a 0 out of 10 on their “Repairability Score”. The lowest anything from Apple has ever gotten is a 1, I believe.

- -
-

Verdict: The Surface Laptop is not a laptop. It’s a glue-filled -monstrosity. There is nothing about it that is upgradable or -long-lasting, and it literally can’t be opened without destroying -it. (Show us the procedure, Microsoft, we’d love to be wrong.)

-
- -

iFixit’s point of view on this is logical, and they’re certainly not alone in wishing for the good old days of user-accessible and user-upgradeable components. But it’s silly to argue that the Surface Laptop is “not a laptop” only because it’s a sealed box. It’s like saying the iPhone is not a phone because it doesn’t have a replaceable battery.

- -

Update: Apple’s AirPods got a 0/10 from iFixit. That just goes to show how little correlation there is between iFixit’s concept of repairability and whether a product is good or not. I consider AirPods to be Apple’s best new product in years.

- - - - ]]>
-
- Standard Ebooks - - - - tag:daringfireball.net,2017:/linked//6.33820 - 2017-06-19T19:40:57Z - 2017-06-20T00:37:38Z - - John Gruber - http://daringfireball.net/ - - Standard Ebooks:

- -
-

Standard Ebooks is a volunteer driven, not-for-profit project -that produces lovingly formatted, open source, and free public -domain ebooks.

- -

Ebook projects like Project Gutenberg transcribe ebooks and -make them available for the widest number of reading devices. -Standard Ebooks takes ebooks from sources like Project -Gutenberg, formats and typesets them using a carefully designed -and professional-grade style guide, lightly modernizes them, -fully proofreads and corrects them, and then builds them to -take advantage of state-of-the-art ereader and browser -technology. […]

- -

Other free ebooks don’t put much effort into professional-quality -typography: they use "straight" quotes instead of “curly” quotes, -they ignore details like em- and en-dashes, and they look more -like early-90’s web pages instead of actual books.

- -

The Standard Ebooks project applies a rigorous and modern -typography manual when developing each and every ebook to ensure -they meet a professional-grade and consistent typographical -standard. Our ebooks look good.

-
- -

What a fantastic project. Project Gutenberg is an amazing library, but their books are a mess typographically. (Via Daniel Bogan.)

- - - - ]]>
-
- The Size of iPhone’s Top Apps Has Increased by 1,000 Percent in Four Years - - - - tag:daringfireball.net,2017:/linked//6.33819 - 2017-06-19T19:23:51Z - 2017-06-19T19:23:52Z - - John Gruber - http://daringfireball.net/ - - Randy Nelson, writing for the Sensor Tower blog:

- -
-

According to Sensor Tower’s analysis of App Intelligence, -the total space required by the top 10 most installed U.S. iPhone -apps has grown from 164 MB in May 2013 to about 1.8 GB last month, -an 11× or approximately 1,000 percent increase in just four years. -In the following report, we delve deeper into which apps have -grown the most.

-
- -

Apple really needs to do something about this. It’s not just that these apps are too big, but some of them issue software updates every week (or even more frequently). It’s a huge waste of bandwidth, time, and on-device storage space.

- - - - ]]>
-
- Microsoft AI Team Achieves Perfect Score on Atari 2600 Ms. Pac-Man - - - - tag:daringfireball.net,2017:/linked//6.33818 - 2017-06-19T19:14:49Z - 2017-06-19T19:17:41Z - - John Gruber - http://daringfireball.net/ - - Dani Deahl writing for The Verge:

- -
-

At long last, the perfect score for arcade classic Ms. Pac-Man has -been achieved, though not by a human. Maluuba — a deep learning -team acquired by Microsoft in January — has created an AI system -that’s learned how to reach the game’s maximum point value of -999,900 on Atari 2600, using a unique combination of reinforcement -learning with a divide-and-conquer method.

-
- -

Unlike the notoriously bad 2600 port of Pac-Man, the Ms. Pac-Man port was both fun and true to the spirit of the coin-op.

- - - - ]]>
-
- Why Reach Navigation Should Replace the Navbar in iOS Design - - - - tag:daringfireball.net,2017:/linked//6.33817 - 2017-06-19T18:52:46Z - 2017-06-19T18:57:25Z - - John Gruber - http://daringfireball.net/ - - Brad Ellis:

- -
-

As devices change, our visual language changes with them. It’s -time to move away from the navbar in favor of navigation within -thumb-reach. For the purposes of this article, we’ll call that -Reach Navigation.

-
- -

This design trend is clearly already underway, and Ellis does a terrific job explaining why it’s a good idea.

- -

I can think of a few factors that led to the original iPhone having a top-of-the-screen UI for navigation. First, at just 3.5 inches diagonally, the whole screen was reachable. But another factor might be as simple as the fact that “navigation” was always at the top on desktops — window titles and controls have always been at the top on Mac and Windows. The iPhone didn’t use windows, per se, but there was a certain familiarity with having the titles and controls like Back/Close/Done buttons at the top. Something like the iOS 10 bottom-heavy design of Apple Maps is wholly different from a desktop UI design — as it should be.

- - - - ]]>
-
- Ben Thompson on Amazon and Whole Foods - - - - tag:daringfireball.net,2017:/linked//6.33816 - 2017-06-19T18:25:15Z - 2017-06-19T18:25:16Z - - John Gruber - http://daringfireball.net/ - - Great piece by Ben Thompson on Amazon’s intended acquisition of Whole Foods:

- -
-

As Mackey surely understood, this meant that AmazonFresh was at a -cost disadvantage to physical grocers as well: in order to be -competitive AmazonFresh needed to stock a lot of perishable items; -however, as long as AmazonFresh was not operating at meaningful -scale a huge number of those perishable items would spoil. And, -given the inherent local nature of groceries, scale needed to be -achieved not on a national basis but a city one.

- -

Groceries are a fundamentally different problem that need a -fundamentally different solution; what is so brilliant about this -deal, though, is that it solves the problem in a fundamentally -Amazonian way.

-
- - - - ]]>
-
- Ikea Details Plans for Furniture Placement App Powered by Apple’s ARKit - - - - tag:daringfireball.net,2017:/linked//6.33815 - 2017-06-19T17:59:44Z - 2017-06-19T17:59:46Z - - John Gruber - http://daringfireball.net/ - - Mitchel Broussard:

- -
-

At WWDC this year, Apple senior vice president of software -engineering Craig Federighi performed a demo of the company’s new -augmented reality platform, ARKit, while mentioning popular -furniture company IKEA as an upcoming partner in the technology. -Similarly, Apple CEO Tim Cook referenced an Ikea AR partnership in -a recent interview with Bloomberg Businessweek.

- -

Now, Ikea executive Michael Valdsgaard has spoken about the -company’s partnership with Apple and ARKit, describing an all-new -augmented reality app that will help customers make “reliable -buying decisions” for Ikea’s big ticket items.

-
- -

Very cool idea — probably the sort of thing that’s going to be common soon. I’m curious how much of a leg up ARKit will give iOS on this front.

- - - - ]]>
-
- Squarespace Domains - - - - tag:daringfireball.net,2017:/linked//6.33814 - 2017-06-18T18:57:06Z - 2017-06-19T03:26:42Z - - John Gruber - http://daringfireball.net/ - - My thanks to Squarespace for sponsoring last week’s DF RSS feed. Squarespace handles everything related to creating, hosting, and maintaining a website, including domain name registration.

- -

Buying a domain from Squarespace is quick, simple, and fun. Search for the domain you want, or type any word or phrase into the search field and Squarespace will suggest some great options. Every domain comes with a beautiful, ad-free parking page, WHOIS Privacy, and a 2048-bit SSL certificate to secure your website — all at no additional cost. Once you lock down your domain, create a beautiful website with one of Squarespace’s award-winning templates. Try Squarespace for free. When you’re ready to subscribe, get 10% off at squarespace.com with offer code “DARING17”.

- - - - ]]>
-
- Designing the Worst Volume Sliders Possible - - - - tag:daringfireball.net,2017:/linked//6.33813 - 2017-06-18T18:54:31Z - 2017-06-18T18:54:32Z - - John Gruber - http://daringfireball.net/ - - This is a fun challenge.

- - - - ]]>
-
- John Markoff to Interview Scott Forstall Next Week - - - - tag:daringfireball.net,2017:/linked//6.33812 - 2017-06-15T19:34:33Z - 2017-06-15T20:03:29Z - - John Gruber - http://daringfireball.net/ - - Blockbuster event next week at the Computer History Museum in Mountain View:

- -
-

How did iPhone come to be? On June 20, four members of the -original development team will discuss the secret Apple project, -which in the past decade has remade the computer industry, changed -the business landscape, and become a tool in the hands of more -than a billion people around the world.

- -

Part 1: Original iPhone Engineers Nitin Ganatra, Scott Herz, and -Hugo Fiennes in Conversation with John Markoff

- -

Part 2: Original iPhone Software Team Leader Scott Forstall in -Conversation with John Markoff

-
- -

It kills me that I can’t make this. Hopefully there will be video.

- -

Here’s the thing: Forstall was obviously a divisive figure inside Apple. He saw himself as an indispensable man after Steve Jobs died, and it turns out he wasn’t.

- -

But there can be no dispute that Forstall led one of the most successful software projects ever undertaken. It’s a cliche to say that they achieved the impossible, but what Forstall’s team achieved was considered by many — including many of the members of the team — impossible. But they did it, and in the ensuing years they kept making iOS better and better. It’s not just that they managed to ship the original iPhone OS in June 2007, but the entire run up through Forstall’s ouster from the company was simply amazing.

- -

Across the company, it’s clear that Forstall’s style was not popular. But I know many people who worked on his iOS team, and most of them loved working for him, or at the very least appreciated working for him. The thing I’ve heard over and over is that Forstall was incredibly demanding, yes, but if you were on his team and did good work he had your back.

- -

Forstall pretty much hasn’t said a damn thing about Apple since he left the company five years ago. So if he opens up at all to Markoff, this could be fascinating. His team’s story about actually implementing the original iPhone remains largely untold.

- - - - ]]>
-
- Brian Merchant Has Tony Fadell on Tape - - - - tag:daringfireball.net,2017:/linked//6.33811 - 2017-06-15T19:16:54Z - 2017-06-15T19:16:55Z - - John Gruber - http://daringfireball.net/ - - Nilay Patel, announcing a special episode of The Vergecast with The One Device author Brian Merchant:

- -
-

And, of course, we talk about the quotes from Tony Fadell and -Brett Bilbrey in the excerpt we just published, in which Fadell -tells a story about Phil Schiller arguing the iPhone should have a -hardware keyboard. Schiller has said the story isn’t true, and -Fadell has tried to walk it back as well.

- -

“So I wasn’t in the room at Apple 10, 15 years ago when this would -have happened,” says Merchant, who has the exchange on tape. “But -this is a quote verbatim as Tony Fadell who was in the room told -it to me. He told me this quote in such detail and he gave such a -vivid account, and I had no reason to believe it was untrue.”

- -

Merchant says the controversy has “blown him away.”

-
- -

I figured Merchant had Fadell’s interview recorded. The quotes were too extensive not to have been recorded. It’s pretty clear what happened: Fadell told Merchant exactly what he’s quoted as saying, but now that he’s seen how it’s playing out, he wants to walk it back. It’s a little late for that.

- - - - ]]>
-
- - - - tag:daringfireball.net,2017://1.33805 - 2017-06-14T03:59:00Z - 2017-06-14T21:22:44Z - - John Gruber - http://daringfireball.net/ - - Highlights from the best of yesterday’s iPad Pro reviews. - Federico Viticci, MacStories, “The 10.5-inch iPad Pro: Future-Proof”:

- -
-

A good way to think about the iPad’s new display with ProMotion -is not the difference between low-res and Retina screens, but -the jump from 30fps to 60fps. You see more of every animation. -Text is more legible when you scroll and doesn’t judder. It’s -hard to explain and it has to be seen and experienced to be -fully understood. Every scroll, page transition, and app launch -animation on the 10.5″ iPad Pro is absurdly smooth to the point -of feeling unrealistic at first — hence the common reaction -that something doesn’t quite compute. But as you spend some time -with the new iPad and start using it on a daily basis, its -display becomes normal and you wish that other Apple displays -were the same.

- -

I’m not even a week into my tests with the 10.5″ iPad Pro, and -I think scrolling on my first-gen 12.9″ iPad Pro looks choppy -now. I’d be surprised if 120Hz displays with ProMotion don’t -expand to the iPhone later this year and other Apple computers -in the future. The combination of hardware and software really -is that good.

-
- -

Last year when True Tone was introduced with the 9.7-inch iPad Pro, Phil Schiller said something to the effect of “Once you get used to True Tone, you can’t go back.” I optimistically took that as a sign that the iPhone 7 would have True Tone. It did not, and the reason is probably that True Tone requires additional hardware sensors on the front face to pick up the ambient light temperature, and the iPhone has less room for additional sensors. But with ProMotion, I’m really hopeful that it’ll make its way into this year’s new iPhones. ProMotion doesn’t require additional sensors — only a super-fast GPU (which the iPhone will have) and intricate software support in iOS (which work Apple has already done for the iPad Pro).

- -

Anyway, it’s really hard to quote just one bit from Viticci’s review. If you only thoroughly read one review of the new iPad Pro, it should be his. Nobody outside Apple cares as much about iPad as he does.

- -
- -

Matthew Panzarino, TechCrunch, “Apple Pays Off Its Future-of-Computing Promise With iPad Pro”:

- -
-

After playing with the new iPad Pro 10.5” for a few days, I am -convinced that it’s fairly impossible to do a detailed review of -it in its current state.

- -

Not because there is some sort of flaw, but because it was clearly -designed top to bottom as an empty vessel in which to pour iOS 11.

- -

Every feature, every hardware advancement, every piece of -understated technical acrobatics is in the service of making -Apple’s next-generation software shine.

-
- -
- -

Dieter Bohn, The Verge, “iPad Pro 10.5 Review: Overkill”:

- -
-

I was all set to complain that increasing the size from 9.7 to -10.5 was not a big enough jump to justify requiring people to buy -new keyboards and accessories. Then I started typing on the -on-screen keyboard and on the new hardware Smart Keyboard. Even -though I’m dubious about Apple’s claim that the software keyboard -is “full size”, I find the slight size increase makes touch typing -much easier. It’s still a little cramped, but it’s much easier to -bounce between this and a real keyboard now.

-
- -

It really does make a difference in typing, and no practical difference at all in terms of holdability.

- -

Bohn again:

- -
-

To me, if you’re going to spend $650 on a computer, it should -almost surely be your main computer. And if you’re going to make -the iPad Pro your main computer, you should probably get more than -64GB of storage and you should also probably get a keyboard to go -with it (to say nothing of the Apple Pencil). It hits the $1,000 -mark very quickly.

-
- -

I don’t agree with the notion that a $650 computer should be your “main computer” at all. Apple stuff isn’t for the budget-conscious — news at 11.

- -
- -

Brian X. Chen, The New York Times, “New iPad Pro Inches Toward Replacing PC, but Falls Short”:

- -
-

Five years later, Mr. Jobs’s successor, Timothy D. Cook, took the -iPad a step further. Unveiling the iPad Pro, a souped-up tablet -that worked with Apple’s keyboard and stylus, he remarked that -people would try the product and “conclude they no longer need to -use anything else, other than their phones.”

- -

That prediction has not appeared to come true. Many professionals -say they use an iPad in addition to a personal computer, and sales -of iPads have shrunk quarter after quarter for more than a year, -an indication that hordes of people were not trading in their PCs -for tablets just yet.

- -

That situation is unlikely to change with Apple’s newest iPad Pro, -which will be released this week. […] But after about a week of -testing the 10.5-inch iPad Pro, I concluded that Apple’s -professional tablet still suffers from some of the same problems -when compared with a laptop.

-
- -

That’s a slanted truncation of Cook’s quote. Cook’s full quote: “Yes, the iPad Pro is a replacement for a notebook or a desktop for many, many people. They will start using it and conclude they no longer need to use anything else, other than their phones.” Chen’s truncation makes it sound like Cook claimed the iPad Pro was a Mac or Windows replacement for everyone. He didn’t. And the fact that the new iPad Pro debuted alongside new MacBooks, MacBook Pros, and even more-megahertz-in-the-box MacBook Airs shows that Apple doesn’t think so either. Update: And I completely forgot to mention the solid updates to the iMacs and the announcement of the iMac Pro.

- -

I prefer a laptop to an iPad Pro” is very different from “A laptop is better than an iPad Pro”. Me, personally, I much prefer working on a MacBook Pro to an iPad Pro. But I can see why others feel the opposite. That’s the whole point of Apple’s strategy of keeping them separate, rather than unifying them Microsoft Surface-style.

- -

iPad’s slowly diminishing sales are a real thing. But I don’t think that can be used as a gauge for whether more and more people are using an iPad as their main computer. And iPad sales are still more than double those of the Mac. There’s no reason to doubt that “many, many people” are concluding they no longer need a Mac or PC.

- -
- -

Andrew Cunningham, Ars Technica, “The 10.5-Inch iPad Pro Is Much More “Pro” Than What It Replaces”:

- -
-

Of all the computers Apple sells, none of them has screens that do -quite as much stuff as the iPad Pros are doing.

- -

That list starts with DCI-P3 color gamut support (new in the -12.9-inch Pro, returning to the smaller one) and an -anti-reflective coating, features also present in recent iMacs and -MacBook Pros. But the True Tone feature, which detects the color -temperature of the ambient light, adjusts the display’s color -temperature to match. Most significantly, the iPad’s refresh rate -has been bumped up to 120Hz, twice the normal 60Hz. The screens in -the iPad Pros are the best screens Apple ships, which is -appropriate for a thing that’s just a giant screen by design.

- -

The 10.5-inch Pro has a 2224×1668 screen, up just a little bit -from the 2048×1536 in 9.7-inch iPads. The density is identical, so -photos and text are exactly the same size they were before; you -can just fit a bit more of them on-screen at once.

-
- -

That’s important to note. There was some clever speculation by Dan Provost a few months ago that the 10.5-inch iPad would have the same pixel dimensions as the 12.9-inch iPad Pro, with a higher pixels-per-inch density. That’s what Apple did with the iPad Mini. The problem with that speculation is that while the math worked out, the size of things on screen would not. Everything would be shrunk by 20 percent. Not everyone’s eyes can handle that. That’s fine for the Mini — which is often used by sharp-eyed children — but not fine for the standard size iPad.

- -

I had been thinking that maybe what Apple would do is what Provost suggested, but offer a choice between standard and zoomed mode like the Plus-sized iPhones do. Nope. I think what they’ve done is better though, because I think a scaled “zoomed” interface would look blurry at just 324 ppi. The iPhone Plus displays have a resolution of 401 ppi.

- -
- -

Harry McCracken, Fast Company, “A Better Window Into The World Of Apps”:

- -
-

You can suss out a lot about Apple’s priorities from the aspects -of a product it leaves alone and the ones it never stops -obsessing over.

- -

Consider the iPad. Every generation of Apple’s tablet since the -first one in 2010 has had the same stated battery life–“up to 10 -hours”–which suggests that the company thinks that shooting for -anything in excess of that would be wasted effort.

- -

That 2010 iPad weighed a pound and a half, and felt a bit hefty in -the hand. With 2013’s iPad Air, Apple whittled that down to about -a pound. And there the mid-sized iPads have stayed, -weight-reduction mission accomplished.

- -

However, when it comes to the iPad’s display, Apple has never been -satisfied to rest on its technological laurels.

-
- - - - ]]>
- ★ iPad Pro Review Roundup
- - - - tag:daringfireball.net,2017://1.33799 - 2017-06-13T20:46:25Z - 2017-06-13T23:15:31Z - - John Gruber - http://daringfireball.net/ - - I do know that Schiller’s hard-charging, brusque style and his obvious political acumen have made him a lot of enemies over the years. It sounds like Fadell is one of them. - The Verge has an exclusive (and lengthy) excerpt from Brian Merchant’s The One Device: The Secret History of the iPhone, which comes out next week. Merchant seemingly has many first-hand sources on the record, including Tony Fadell and perhaps Scott Forstall. (I say “perhaps” because it’s not clear from the excerpt whether Forstall spoke to Merchant, or if Merchant got the Forstall quotes from somewhere else. It seems like there should be a lot more from Forstall in this story if he actually talked to Merchant.)

- -

But Fadell spoke to Merchant extensively, including this shot at Phil Schiller:

- -
-

The iPod phone was losing support. The executives debated which -project to pursue, but Phil Schiller, Apple’s head of marketing, -had an answer: Neither. He wanted a keyboard with hard buttons. -The BlackBerry was arguably the first hit smartphone. It had an -email client and a tiny hard keyboard. After everyone else, -including Fadell, started to agree that multitouch was the way -forward, Schiller became the lone holdout.

- -

He “just sat there with his sword out every time, going, ‘No, -we’ve got to have a hard keyboard. No. Hard keyboard.’ And he -wouldn’t listen to reason as all of us were like, ‘No, this works -now, Phil.’ And he’d say, ‘You gotta have a hard keyboard!’” -Fadell says.

-
- -

I don’t know if it’s true or not that Schiller was singlehandedly pushing for a Blackberry-style keyboard. But even if true, it only looks foolish in hindsight, especially if this argument took place before the iPhone’s software team had come up with a proof-of-concept software keyboard. Today it’s clear that the iPhone needed a good keyboard, and that a touchscreen keyboard can be a good keyboard. Neither of those things was obvious in 2005. And in the context of this story, it’s clear that at the time of this purported argument, Steve Jobs and Apple weren’t yet sure if the iPhone should be a pocket-sized personal computer or a consumer electronics product that would have no more need for a keyboard (hardware or software) than an iPod did. My guess is that Schiller was insisting that the iPhone needed to be a personal computer, not a mere gadget, and it wasn’t unreasonable to believe a software keyboard wouldn’t be good enough. For chrissakes there were critics who insisted that the iPhone’s software keyboard wasn’t good enough for years after the iPhone actually shipped.

- -

I do know that Schiller’s hard-charging, brusque style and his obvious political acumen have made him a lot of enemies over the years. It sounds like Fadell is one of them.

- -

So I’ll just say this: this story about Phil Schiller pushing for a hardware keyboard comes from one source (so far — if anyone out there can back that up, my window is always open for little birdies), and that one source is the guy who admittedly spent over a year working on iPhone prototypes with a click wheel interface.

- -

Then there’s this:

- -
-

Schiller didn’t have the same technological acumen as many of the -other execs. “Phil is not a technology guy,” Brett Bilbrey, the -former head of Apple’s Advanced Technology Group, says. “There -were days when you had to explain things to him like a -grade-school kid.” Jobs liked him, Bilbrey thinks, because he -“looked at technology like middle America does, like Grandma and -Grandpa did.”

-
- -

Hats off to Bilbrey for putting his name on this quote, but having spoken to Schiller both on- and off-the-record many times, the idea that he “looks at technology … like Grandma and Grandpa did” and needs things explained to him “like a grade-school kid” is bullshit. Especially off-the-record, Schiller can drill down on technical details to a surprising degree. I don’t know what Schiller did to piss off Bilbrey, but Bilbrey either has a huge chip on his shoulder or was severely misquoted by Merchant.1

- -

Anyway, I sure wish this book excerpt had come out before my live episode of The Talk Show last week — now I do have one more question I wish I’d gotten to ask Schiller.

- -
-
-
    -
  1. -

    Here’s a story from Yoni Heisler for Network World on Brett Bilbrey’s retirement from Apple in 2014. Bilbrey headed Apple’s Technology Advancement Group. Merchant describes Bilbrey as having led “Apple’s Advanced Technology Group”. It’s a small detail, and the names are clearly similar, but the Advanced Technology Group was Larry Tesler’s R&D division at Apple, from 1986-1997. It was among the numerous divisions and products that Steve Jobs shitcanned after he rejoined the company. ↩︎

    -
  2. -
-
- - - - ]]>
- ★ The Knives Come Out for Phil Schiller in Brian Merchant’s ‘The One Device: The Secret History of the iPhone’
- - - - tag:daringfireball.net,2017://1.33796 - 2017-06-12T22:33:42Z - 2017-06-13T23:15:34Z - - John Gruber - http://daringfireball.net/ - - The new iPad Pro hardware is almost too good to be true, but the real iPad story Apple unveiled last week is iOS 11. - I’ve spent the last week using a new 10.5-inch iPad Pro, and this is, in many ways, the easiest product review I’ve ever written. There are several significant improvements to the hardware, and no tradeoffs or downsides. There is no “but”.

- -
    -
  • Display: The new iPad Pros have the best displays of any computer I’ve ever seen. True Tone plus ProMotion is simply terrific. (The first generation 12.9-inch iPad Pro didn’t have True Tone; with these new models, the only noticeable difference between the 12.9- and 10.5-inch models is the size.) You really do have to see the 120 Hz refresh rate in person — and play with it while scrolling content on screen — to get it. You can actually read text as it’s moving during a scroll. It’s not as significant as the jump from non-retina to retina, but it’s in that ballpark.

  • -
  • Pencil: The latency of the Apple Pencil on a first-generation iPad Pro is the best I had ever seen for any stylus on any device at any price. The latency of the Apple Pencil on the new iPad Pro is so much better — so much closer to ink-on-paper imperceptibility — that you have to try it to believe it. It’s the one thing that really makes the first-gen iPad Pro feel “slow”.

  • -
  • Size: The increase in size is perfect. The footprint for a “regular” iPad has, until now, remained unchanged since the original iPad in 2010. That 9.7-inch display size was nearly perfect. This 10.5-inch display size is better though. Apple said during the keynote that typing on the on-screen keyboard is surprisingly better given just a bit more room, and I agree. And typing on the Smart Keyboard cover is way better than on last year’s 9.7-inch iPad Pro. In hand it doesn’t feel bigger at all. It feels like there were no trade-offs whatsoever in increasing the display size and overall device footprint. Part of that is because the weight has remained completely unchanged. I have had zero problems — not one — with the decreased bezel area. Apple’s inadvertent touch detection game is on point.

  • -
  • Battery: Battery life is great, as expected.

  • -
  • Performance: Apple’s in-house chip team continues to amaze. No one buys an iPad because of CPU benchmarks, but the new iPad Pro’s CPU performance is mind-boggling. Forget about comparisons to the one-port MacBook — the iPad Pro blows that machine out of the water performance-wise. The astounding thing is that the new iPad Pro holds its own against the MacBook Pro in single-core performance — around 3,900 on the Geekbench 4 benchmark for the iPad Pro vs. around 4,200–4,400 for the various configurations of 13- and 15-inch MacBook Pros.1 Multi-core performance has effectively doubled from the first generation of iPad Pro. That sort of year-over-year increase just doesn’t happen anymore, but here we are. The new iPad Pro gets a multi-core Geekbench 4 score of around 9200; the brand-new Core M3-based MacBook gets a multi-core score of around 6800. Again, this isn’t why people buy iPads — the iPad took off like a rocket in 2010 back when it was way slower (way way way slower) than even the slowest MacBook — but I think it is vastly underappreciated just how significantly Apple’s chip team is pulling ahead of the industry, especially Intel.2

  • -
- -
- -

All that said, the real story of these new iPad Pro models can’t be told today, because that story is iOS 11. I think iOS 11’s iPad-focused features are the entire reason why Apple waited until WWDC to unveil them. They could have held an event for them back in April, when they released the new starting-at-just-$329 9.7-inch iPad, but if they did, the only new software they could have demoed was Clips. I love Clips, but it’s just a fun little tool and doesn’t show off anything particular to iPad compared to iPhone.

- -

Again, the new iPad Pro hardware is almost too good to be true, but the iPad story Apple unveiled last week is iOS 11.

- -

It’s not fair to review a product running a developer beta of the OS — let alone the first (and generally buggiest) beta. So let’s stop the “review” right here: the new iPad Pros running iOS 10.3.2 are the best iPads ever made. You shouldn’t hesitate to buy one today, and if you do get one now, you should wait until iOS 11 ships in the fall to upgrade, or at the very least wait for a non-developer public beta of iOS 11 this summer before upgrading.

- -

But if you are reckless enough to install the iOS 11 beta on the new iPad Pro? Holy smokes is this better. I used the iPad Pro for a full week with iOS 10.3.2 because that’s the product that’s shipping, but after upgrading to iOS 11 beta 1 this morning and using it to write this entire review,3 I’m just blown away by how much more useful this machine is, and how much easier it is to work with 5 or 6 apps at a time.

- -

I would never recommend running a beta of any OS on any device that’s used for production purposes, so don’t take this as such, but for me personally, I can’t see going back to iOS 10.3.2 on any iPad that can handle it. It feels like a hand has been untied from behind my back, and this amazing hardware has finally been allowed to run free.

- -
-
-
    - -
  1. -

    You can browse Geekbench’s database of results for Mac and iOS↩︎

    -
  2. - -
  3. -

    Apple’s A10X chip is so high-performing that I think it’s put Apple in a slightly uncomfortable position marketing-wise. They can’t brag about it fully without making Intel (and by implication, their own MacBooks) look bad, and Intel remains an important partner for Apple. I don’t think it’s a coincidence that the most impressive iPad demo during the keynote (and the one that contained the most bragging about its performance compared to PCs) was done by a third-party developer — Ash Hewson of Serif, demonstrating Affinity Photo — not Greg Joswiak or anyone else from Apple. ↩︎︎

    -
  4. - -
  5. -

    Full disclosure: I went back to my Mac to write these 3 footnotes. That’s due more to the byzantine way I mark up footnotes than any limitation inherent to iOS vs. MacOS. But it feels worth noting. ↩︎︎

    -
  6. - -
-
- - - - ]]>
- ★ The 2017 iPad Pros
- - - - tag:daringfireball.net,2017://1.33791 - 2017-06-10T20:20:10Z - 2017-06-10T20:20:12Z - - John Gruber - http://daringfireball.net/ - - Given how many legitimate developers are still having problems getting their apps approved due to seemingly capricious App Store reviewer decisions, it’s doubly outrageous that these apps have made their way onto the store in the first place. These are the *exact* sort of apps that the App Store review process should be primarily looking to block. - Great investigative piece by Johnny Lin looking into a top-10 highest grossing app named “Mobile protection :Clean & Security VPN” (punctuation and grammatical errors sic), from a developer named Ngan Vo Thi Thuy:

- -
-

“Full Virus, Malware scanner”: What? I’m pretty sure it’s -impossible for any app to scan my iPhone for viruses or malware, -since third party apps are sandboxed to their own data, but let’s -keep reading…

- -

“You will pay $99.99 for a 7-day subscription”

- -

Uhh… come again?

-
- -

There should be no “virus and malware” scanners in the App Store. None. iOS does not need anti-virus software. The App Store sandboxing rules mean that anti-virus software couldn’t really do anything useful anyway. And by allowing them to be listed on the store, it creates the false impression that Apple thinks you might need anti-virus software.

- -

But do-nothing anti-virus utilities that are scamming people into $100/week subscriptions? That’s downright criminal.

- -

Lin shows that “Mobile protection :Clean & Security VPN” is not alone. The productivity top-grossing list is riddled with similar scam apps.

- -

Given how many legitimate developers are still having problems getting their apps approved due to seemingly capricious App Store reviewer decisions, it’s doubly outrageous that these apps have made their way onto the store in the first place. These are the exact sort of apps that the App Store review process should be primarily looking to block.

- -

And there is no excuse for Apple not having flagged them after the fact, once they started generating significant revenue. It’s downright mind boggling that this horrendous “Mobile protection :Clean & Security VPN” app made it all the way into the top 10 without getting flagged.

- -

Based on Lin’s research, the pattern is simple:

- -
    -
  1. Create a scammy utility app and get it into the store. Make it a free download with an expensive in-app subscription.
  2. -
  3. Bid on common keywords like “virus”, “VPN”, and “wi-fi”.
  4. -
  5. Get tens of thousands of downloads thanks to the top-of-results placement from the ad.
  6. -
  7. Spring the in-app subscription prompt on your users and make money with a response rate of even just a fraction of 1 percent.
  8. -
- -

Apple needs to remove these apps from the App Store, and prevent such apps from getting into the store in the first place. They should reconsider the effects of allowing developers to buy their way to the top spot in search results. And they should police the top-grossing lists for apps that are pulling scams — the most important scams to catch are the successful ones.

- -

Lastly, every single dollar these apps have generated should be refunded to the victims of these scams.

- - - - ]]>
- ★ In-App Purchase Scams in the App Store
- - - - tag:daringfireball.net,2017://1.33772 - 2017-06-02T05:41:06Z - 2017-06-02T06:13:18Z - - John Gruber - http://daringfireball.net/ - - The next batch of tickets will go on sale today, Friday, at 1p ET/10a PT. Given what happened Wednesday, I expect them to sell out in a few minutes. - On Wednesday I put the first 500 tickets on sale for next week’s live show from WWDC. They sold out in 7 minutes.

- -

The California Theatre in San Jose has both an orchestra level and a balcony. That first bunch of tickets separated the two. After talking with the staff at the theater today, they recommended making all tickets general admission and allowing their ushers to fill the orchestra level first, and then direct remaining ticket holders to the balcony. So, all tickets, including those sold Wednesday, are now simply general admission. Everyone paid the same price, so I think this is fair, but I do apologize for any confusion. The theater is beautiful, and there are no bad seats.

- -

The next batch of tickets will go on sale today, Friday, at 1p ET/10a PT. Given what happened Wednesday, I expect them to sell out in a few minutes. I hate writing that because it sounds braggy, but I’m putting it out there just as fair warning. You’re going to have to act quick and maybe get lucky.

- -

If you want a ticket and wind up not getting one, there will be a live audio stream for everyone to listen to. This year we are not going to attempt to stream live video. Instead we’re going to work hard to get edited video of the event up on the web as soon as possible after the show is over. If you just can’t wait, listen to the live audio. If you want to see the show, wait for the video — it should be up some time on Wednesday at the latest.

- -

If you do get a ticket or already have one:

- -
    -
  • All seats are general admission, with no distinction between orchestra level and the balcony.
  • -
  • Put your ticket in Apple Wallet and bring ID to show at the door. (If you don’t have an iPhone (?) bring a copy of the ticket PDF.)
  • -
  • Doors open at 6p, and there will be an open bar. Find a seat, grab a beverage, and mingle with your fellow fans of the show.
  • -
  • The show itself should start at 7p.
  • -
- - - - ]]>
- ★ Update on The Talk Show Live From WWDC 2017
diff --git a/RSParser/Tests/RSParserTests/Resources/DaringFireball.html b/RSParser/Tests/RSParserTests/Resources/DaringFireball.html deleted file mode 100755 index 690218708..000000000 --- a/RSParser/Tests/RSParserTests/Resources/DaringFireball.html +++ /dev/null @@ -1,1341 +0,0 @@ - - - - - Daring Fireball - - - - - - - - - - - - -
- - - - - -
- -
-
-San Bernardino DA Says Seized iPhone May Hold ‘Dormant Cyber Pathogen’  -
-
-

Ars Technica:

- -
-

“The iPhone is a county owned telephone that may have connected to the San Bernardino County computer network. The seized iPhone may contain evidence that can only be found on the seized phone that it was used as a weapon to introduce a lying dormant cyber pathogen that endangers San Bernardino’s infrastructure,” according to a court filing (PDF) by Michael Ramos, the San Bernardino County district attorney.

-
- -

Sounds to me like Ramos has watched Skyfall too many times.

- -
- - -
- - -
-
-Igloo, a Modern Intranet  -
-
-

My thanks to Igloo for sponsoring this week’s DF RSS feed. Collaboration can be incredibly fragmented today — files shared one way, messaging via various chat apps, email lists for groups, etc. It can be overwhelming.

- -

That’s why you should try Igloo. It combines department spaces, team calendars, corporate file sharing, internal communications capabilities, social features, and more. It’s really easy both to use and to configure. Igloo is an intranet you’ll actually like. Try Igloo today, free of charge.

- -
- - -
- - -
-
-Apple Hosts Public Letter From San Bernardino Victim to Judge Sheri Pym (PDF)  -
-
-

Salihin Kondoker:

- -
-

When I first learned Apple was opposing the order I was frustrated that it would be yet another roadblock. But as I read more about their case, I have come to understand their fight is for something much bigger than one phone. They are worried that this software the government wants them to use will be used against millions of other innocent people. I share their fear.

- -

I support Apple and the decision they have made. I don’t believe Tim Cook or any Apple employee believes in supporting terrorism any more than I do. I think the vicious attacks I’ve read in the media against one of America’s greatest companies are terrible.

-
- -

The battle is being fought both in the courtroom, and in the court of public opinion. Support like this helps Apple with the latter — which in turn helps with the former.

- -
- - -
-Box Chief Information Security Officer’s Perspective on Apple and the FBI  -
-
-

Joel de la Garza:

- -
-

I’ve been working to help secure computer systems for the entirety of my professional career. It is incredibly difficult to build computer systems that are not vulnerable to attack. As we’ve seen, a number of companies and governments have had great difficulty protecting the front door of their computer systems. I’ve dedicated my career to making sure our systems are designed, built, and operated to the most secure standards. And even with that tremendous investment, bugs still happen. Due to the many layers of security controls built into our systems software bugs are usually not damaging or catastrophic in nature. But peeling away those layers of control to create a backdoor means that even the most basic security bug could potentially have a catastrophic effect. This reality is missing from our current debate about the FBI’s order to Apple in the San Bernardino tragedy.

-
- -
- - -
-@AppleSupport  -
-
-

Neat: Apple has a new Twitter account for tips, tricks, and support. Right now they’re answering 3-4 questions per minute.

- -
- - -
-Full Frontal With Samantha Bee  -
-
-

Four episodes in and I’m loving this show. The no-desk thing threw me off at first — the staging has a Tosh.0 vibe — but it works. The show has a fast pace and Bee jabs hard. A desk is leisurely, and Full Frontal is anything but.

- -
- - -
- - -
-
-The Talk Show: ‘Occupy Portland’  -
-
-

Special guest John Moltz returns to the show. Topics include the Apple/FBI encryption fight, Apple’s upcoming event and the products they’re expected to announce. And Campo Santo’s fantastic new video game Firewatch.

- -

Sponsored by:

- -
    -
  • Backblaze: Online backup for $5/month. Native. Unlimited. Unthrottled. Uncomplicated.
  • -
  • Squarespace: Build it beautiful. Use code GRUBER for 10% off your first order.
  • -
  • MailRoute: Hosted spam and virus protection for email. Use this link for 10 percent off for the life of your account.
  • -
- -
- - -
-Fiat Chrysler CEO: Apple Should Not Try Making a Car on Its Own  -
-
-

Reuters:

- -
-

Speaking to journalists at the Geneva auto show, Marchionne said -there was sufficient capacity available among car makers to deal -with Apple’s requirements and it would make more sense for them to -partner with a car manufacturer rather than become an actor itself -in such a “complex business”.

-
- -

I can see it now: the Fiat Rokr.

- -
-

“If they have any urges to make a car, I’d advise them to lie down -and wait until the feeling passes,” Marchionne told journalists. -“Illnesses like this come and go, you will recover from them, -they’re not lethal.”

-
- -

They’ve struggled for a few years here, figuring out how to make a decent car. Phone guys are not going to just figure this out. They’re not going to just walk in.

- -
- - -
-David Sparks on Apple Notes  -
-
-

David Sparks:

- -
-

Then I stated using Apple Notes and the strangest thing happened. -I liked it. Not only is Apple Notes a contender, Apple has -continued to refine the product. Just last week we got a new beta -of an upcoming Mac OS X release that includes additional Apple -Notes features. One of those new features is the ability to import -Evernote and plain text files. It seemed to me like a perfect -excuse to slurp in the rest of my nvALT database so I could really -push the application’s limits. Now I’ve got 787 notes in my Apple -Notes database. It’s growing daily.

- -

So first this was all a big experiment to see what was wrong with -Apple Notes and then I just started using the application. I -didn’t admit to myself, or anyone else, that I become an Apple -Notes user but apparently I have.

-
- -

Now that its syncing is based on CloudKit instead of IMAP, and with the features that have been added in iOS 9 and Mac OS X 10.11, Apple Notes is a solid notes app. I echo Sparks on this complaint:

- -
-

That doesn’t mean Apple Notes is without fault. I wrote before, -and it still remains true, that the text size on the Mac version -is just too small. They keep adding new features with the betas -and it keeps amazing me that they don’t address this problem.

-
- -

It’s almost mind boggling that you can’t change the default font size on the Mac. It’s not just a matter of preference, it’s a genuine accessibility problem. You can hit ⌘+ to increase the font size of the current note, but there’s no way to change the default for new notes. So if Helvetica 12 is too small for you to read, you’re stuck hitting ⌘+ for every single note.

- -

Update: And why is it Helvetica instead of San Francisco? Does the Apple Notes Mac team live in a cave?

- -
- - -
-A Complete History of the Millennium Falcon  -
-
-

Michael Heilemann, writing for his remarkable Kitbashed:

- -
-

One of the things I find so interesting about Star Wars is how the -creative process so clearly wasn’t locked from the beginning. It -was a long and winding road, and throughout writing the essays for -Kitbashed I’ve found that despite intense pressure there was -always an energetic adventurousness with ideas which inevitably -lead to some of the most iconic designs in film history.

- -

The Falcon is a great example of that, specifically because the -final design is so distinct. It makes it a much more enticing to -try to decipher how it came about.

- -

While I’ve been pursuing this subject for years, it wasn’t until I -starting putting together this essay that I finally began to find -some of the finer details of the Falcon’s creation.

-
- -

The Millennium Falcon is my favorite thing in all of Star Wars — and it was almost something altogether different. The original design was a fine space ship, but it had none of the character the actual Falcon has.

- -
- - -
-Former Google CEO Schmidt to Head New Pentagon Innovation Board  -
-
-

Andrea Shalal, reporting for Reuters:

- -
-

Eric Schmidt, the former chief executive officer of Google, will -head a new Pentagon advisory board aimed at bringing Silicon -Valley innovation and best practices to the U.S. military, Defense -Secretary Ash Carter said on Wednesday. Carter unveiled the new -Defense Innovation Advisory Board with Schmidt during the annual -RSA cyber security conference in San Francisco, saying it would -give the Pentagon access to “the brightest technical minds focused -on innovation.”

- -

Schmidt, now the executive chairman of Alphabet Inc (GOOGL.O), the -parent company of Google, said the board would help bridge what he -called a clear gap between how the U.S. military and the -technology industry operate.

-
- -

Coming soon to a headline near you: Google to Become Major Defense Contractor.

- -
- - -
-A Procastinator on Doing a TED Talk About Procrastination  -
-
-

Tim Urban:

- -
-

All TED speakers do a fully mic’ed and dressed rehearsal on the -real stage the weekend before the conference starts. Mine was -three days before my talk — and it was pretty rough, confirming -to me and everyone present that I was officially not a fraud when -it came to my topic. The irony of a guy rehearsing his TED Talk -about how he’s a bad procrastinator, and being clearly -underprepared while doing so, was not lost on anyone.

-
- -

This whole thing was white-knuckle reading for me.

- -
- - -
-‘Warrant-Proof Places’  -
-
-

From The Financial Times’s report on yesterday’s Apple/FBI hearing before Congress:

- -
-

“Our job is simply to tell people there is a problem,” Mr Comey -said. “If there are warrant-proof spaces in American life, what -does that mean and what are the costs of that?” He added: “The -tools we use to keep you safe are becoming less and less -effective.”

-
- -

There have always been “warrant-proof places” containing information inaccessible to law enforcement: our minds. I support the right to use unbreakable encryption for the same reason I support Fourth and Fifth Amendment rights, especially the right to remain silent.

- -
- - -
-Motherboard: ‘The Apple-FBI Encryption Hearing Was Unexpectedly Hostile to the FBI Director’  -
-
-

Sarah Jeong, reporting for Motherboard:

- -
-

A couple of representatives were openly hostile to Comey, but most -launched passive aggressive, loaded questions at the FBI director. -Even though the representatives (both Democrats and Republicans) -were mostly polite, the tone of the the questioning was a huge -departure from how the House Judiciary Committee typically -addresses Comey.

- -

“I would be deeply disappointed if it turns out the government is -found to be exploiting a national tragedy to pursue a change in -the law,” Rep. John Conyers (D-MI) told Comey. […] The questions -got more hostile. Rep. Conyers asked Comey if the San Bernardino -case was an “end-run around this committee” — a loaded question -that Comey of course denied. […]

- -

After that, Rep. Darrell Issa (R-CA) opened his questioning by -quoting the late Justice Antonin Scalia: “There is nothing new in -the realization that the Constitution sometimes insulates the -criminality of a few in order to protect the privacy of all of -us.” Issa’s questioning was overtly hostile in tone, delving deep -into the technical details of the iPhone 5c. Comey was at loss, -admitting, “I have not answered the questions you have asked me -today and I am not entirely sure I understand the questions.”

- -

Rep. Zoe Lofgren (D-CA) then said to Comey, “As I was hearing your -opening statement talking about a world where everything is -private, it may be the alternative is a world where nothing is -private. Because once you have holes in encryption, the question -is not if but when those holes will be exploited.”

-
- -

I’m actually not surprised at the hostility toward Comey. Democrats tend to support civil liberties against overreach from law enforcement, and Republicans — especially those in today’s House of Representatives — are extremely skeptical of an ever-more-powerful federal government. And both Republicans and Democrats yesterday seemed aware that the FBI’s use of the All Writs Act is, as Conyers put it, “an end-run around” Congress.

- -

If there’s one thing that can unite both parties in today’s polarized Congress, it is the protection of congressional authority. The idea that the Department of Justice (which is part of the Executive Branch) and the Judicial Branch could dictate the terms of this debate is not going to fly.

- -

Update: To be clear, there was also hostility toward Apple. That was expected by everyone. Some congresspeople are card-carrying members of the Golden Key Wizard Society.

- -
- - -
-February 2016 Was Very Warm  -
-
-

Eric Holthaus, writing for Slate:

- -
-

Keep in mind that it took from the dawn of the industrial age -until last October to reach the first 1.0 degree Celsius, and -we’ve come as much as an extra 0.4 degrees further in just the -last five months. Even accounting for the margin of error -associated with these preliminary datasets, that means it’s -virtually certain that February handily beat the record set just -last month for the most anomalously warm month ever recorded. -That’s stunning.

-
- -

February is typically brutally cold here in Philadelphia. It’s the month when I question why the hell I live here. This year, we had eight days with a high temperature in the 60s, and another four in the 50s. There were only four days where the temperature didn’t rise above freezing.

- -
- - -
- - -
-
-Silvia Killingsworth Takes Over The Awl  -
-
-

The Awl:

- -
-

We’re thrilled to announce that Silvia Killingsworth will be -joining us here in April. Silvia is currently the Managing Editor -of the New Yorker, where she has spent the last seven years -managing the workflow of the magazine. (You may also know her from -the web’s greatest food vertical, De Gustibus.) Silvia’s -breadth of experience and wealth of ideas and just genuine -enthusiasm (an emotion you may have noticed as being in short -supply over the last, say, seven years here) about things make her -the clear and obvious choice to head The Awl as it evolves into -its next stage of life.

-
- -

Great hire. Go Awl.

- -

Update: And in more media industry news, Nick Bilton has left The New York Times to become a special correspondent for Vanity Fair. Another great hire.

- -
- - -
-The Q4 2015 Smartphone Scorecard: Apple Gazes Down at the Rat Trap  -
-
-

Smart piece from Charles Arthur on the state of the handset industry.

- -
- - -
-Steve Ballmer Dunks During Clippers Halftime Show  -
-
-

Not sure about the Clips’ new mascot, though.

- -
- - -
- - -
-
-Apple Wins Major Court Victory Against FBI in a Case Similar to San Bernardino  -
-
-

Glenn Greenwald and Jenna McLaughlin, reporting for The Intercept:

- -
-

Judge Orenstein applied previous legal decisions interpreting the -AWA and concluded that the law does not “justif[y] imposing on -Apple the obligation to assist the government’s investigation -against its will.” In a formulation extremely favorable to Apple, -the judge wrote that the key question raised by the government’s -request is whether the AWA allows a court “to compel Apple — a -private party with no alleged involvement in Feng’s criminal -activity — to perform work for the government against its will.”

- -

The court ruled that the law permits no such result — both -because relevant law contains limits on what companies like Apple -are required to do, and because Congress never enacted any such -obligations. Moreover, the judge said of the government’s -arguments for how the AWA should be applied: “The implications of -the government’s position are so far-reaching — both in terms of -what it would allow today and what it implies about congressional -intent in 1789 — as to produce impermissibly absurd results.”

-
- -

This seems like great news for Apple and supporters of civil liberties in this case.

- -

Read Orenstein’s decision here.

- -
- - -
-San Bernardino Survivor’s Husband to Judge: Terrorist iPhone ‘Unlikely’ to Hold Valuable Information  -
-
-

I hope I don’t have to keep repeating this, but this is the wrong argument to make. The implication is that the result should be different if the iPhone in question was “likely” to contain valuable information. That’s wrong. Civil liberties apply equally in all situations.

- -

Don’t get me wrong — I’m glad they’re saying this particular iPhone is unlikely to actually contain useful information. But someday there will be a locked iPhone that is either likely or certain to contain useful information.

- -
- - -
-Fred Wilson: ‘The Twitter Contradiction’  -
-
-

Fred Wilson:

- -
-

I just don’t understand the narrative around Twitter. “It is in -trouble. It isn’t growing. It’s time has come and gone. The kids -all use Snapchat and Instagram.”

- -

That last part is true, to a degree. But it isn’t as simple -as that.

- -

The presumptive Republican nominee for President of the United -States has largely conducted his campaign on Twitter and in -massive public appearances that feel like rock concerts. He has -avoided the traditional media channels and taken his message -direct to the people on Twitter. Not on Facebook. Not on -Instagram. Not on Snapchat. Not on Pinterest. Not on his website -or mobile app. On Twitter.

-
- -

He makes a good point, but I don’t think there’s a contradiction. On the one hand, Twitter is a powerful publishing platform that has become the de facto official medium for famous people to make public statements about what is going on right now.

- -

The problem is, that’s not the description of a social network. It’s a description of a publishing platform. Twitter’s trouble is that it’s being viewed by investors as a social network.

- -
- - -
-Getting Called Up From the Big Leagues  -
-
-

M.G. Siegler, on Bill Simmons putting his new publication, The Ringer, on Medium:

- -
-

In a way, it almost feels like the thing to do now is the opposite -of what is typical in professional sports. In most leagues, -athletes play in minor leagues (or college) before graduating up -to the big leagues. In our new era of publishing, writers may -start at the big leagues, building up their skills and brands, -before venturing out on their own (or with a group of peers).

-
- -
- - -
-The Washington Post on Industry Support for Apple in Encryption Fight  -
-
-

Ellen Nakashima, writing for The Washington Post:

- -
-

Former Justice Department official Jennifer Daskal said both sides -are overstating their arguments. “The government is wrong to say -this is just about one case,” said Daskal, a law professor at -American University. “On the other hand, it is wrong to say that -if Apple loses this case, there’s absolutely no limits to what the -government can order a company to do” in cases involving encrypted -communications.

-
- -

This is false equivalence. The government really is wrong about this case being about just this one particular phone. But nobody (and certainly not Apple) is using words like “absolutely no limits to what the government can order a company to do” to describe what will happen if the government wins and sets precedent. The results will be significant, and I think chilling — but not limitless. This is just a bullshit quote to make the story sound “balanced”.

- -
-

One argument that companies and civil liberties groups are -expected to make is that if the government’s order is upheld, -then the FBI might be able to order a technology firm to create, -say, malicious software to send to a user’s device in the form of -a routine update. “That is the third rail for tech companies — -to be forced to deliver a software update that breaks the -security of the device,” said Alex Abdo, a staff attorney for the -American Civil Liberties Union, which is also filing a brief in -support of Apple.

-
- -

This would be one of the worst case scenarios I can imagine.

- -
- - -
-The State of Apple Music Connect  -
-
-

Dave Wiskus:

- -
-

If Connect is a social network, it fails miserably. There’s -nothing inherently social about the experience, which feels more -like a local bulletin board than a way for artists to engage -with fans.

- -

It’s also not a very good broadcast medium. Sure, I can post to -Connect and share out to Twitter and whatnot, but why? There’s -nothing unique or powerful about Apple’s system that makes it a -good hub. Because I have no idea how many followers we have, I -can’t even make a numerical argument for Connect-first posting. -And since we can’t even invite people from other places to follow -us on Connect, there’s no incentive to try.

- -

As a fan, it’s a confusing mess. As an artist, it’s a black hole. -All media, no social.

-
- -

Connect was a big part of the Apple Music introduction back in June, but I haven’t heard a word about it since other than when Dave writes about it.

- -
- - -
-Apple’s Statement to Congress on the FBI Warrant Fight  -
-
-

Apple general counsel Bruce Sewell testifies before Congress tomorrow. From his prepared opening statement:

- -
-

As we have told them — and as we have told the American public — -building that software tool would not affect just one iPhone. It -would weaken the security for all of them. In fact, just last week -Director Comey agreed that the FBI would likely use this precedent -in other cases involving other phones. District Attorney Vance has -also said he would absolutely plan to use this on over 175 phones. -We can all agree this is not about access to just one iPhone.

- -

The FBI is asking Apple to weaken the security of our products. -Hackers and cyber criminals could use this to wreak havoc on our -privacy and personal safety. It would set a dangerous precedent -for government intrusion on the privacy and safety of its -citizens.

-
- -
- - -
-It Doesn’t Matter Whether the San Bernardino iPhone Contains Useful Information  -
-
-

Jeff Gamet, writing for The Mac Observer:

- -
-

The iPhone recovered from Syed Farook after he shot and killed 14 -coworkers and then died in a shootout with police most likely -doesn’t hold any valuable information. So says San Bernardino -police chief Jarrod Burguan. Chief Burguan was asked about the -phone during an NPR interview and he replied:

- -
-

I’ll be honest with you, I think that there is a reasonably good -chance that there is nothing of any value on the phone. What we -are hoping might be on the phone would be potential contacts that -we would obviously want to talk to.

-
-
- -

There’s a small point to be made here, insofar as it suggests the FBI is being disingenuous. They’re saying that it’s not about precedent, it’s just about this one phone, this one investigation. But the real reason they’re making a big deal out of it is that it’s politically useful. The phone itself likely isn’t important but the situation surrounding the phone — “terrorism” and the tragedy of 14 innocent people being killed — lends sympathy to their desire for access to encrypted devices all the time.

- -

But for those of us on Apple’s side, this is not a point to hang our hats on. Even if law enforcement claimed to know with certainty that the phone contained useful information, Apple’s arguments would all still stand. Eventually there will be such a phone.

- -

And, likewise, I’m glad law enforcement is doing their best to check the contents of the phone. We want law enforcement to pursue all leads — within the confines of the law — even those that are unlikely to produce useful information.

- -
- - -
-Dan Frommer Named Editor in Chief at Recode  -
-
-

Kara Swisher:

- -
-

As Re/code has grown and morphed, we have always been on the -lookout for great talent to take the site to a new level.

- -

That’s why I’m very excited to announce that we’ve hired Dan -Frommer as the new editor in chief of Re/code. Dan brings our site -the energy, curiosity and tech-savvy we need to succeed in digital -publishing, an industry that gets more exciting — and challenging -— daily.

-
- -

Congratulations, pal.

- -
- - -
- - -
-
-Apple Product Event: Monday March 21  -
-
-

Kara Swisher, writing at Recode, broke the news:

- -
-

Attention Apple nerds, investors, media and everyone else who -needs to know when Tim Cook’s next product event is going to be -held: It’s going to be the week of March 21.

- -

Or to put it another way, it’s not going to be on March 15, the -time frame that other outlets previously reported, according to -several sources. It is not clear if the event was moved or if this -was the same timing as Apple had always planned.

-
- -

Swisher doesn’t have the exact date, although the <title> tag on her story reads “Apple Product Event Will Be Held March 22”. John Paczkowski (who usually gets these leaks first), confirms the week change, and says the event will be on Monday 21 March:

- -
-

Sources in position to know say the company has settled on March -21st as the date it will show off a handful of new products. These -people declined to say why Apple postponed the date by a week, but -it’s worth noting that it is one day prior to the company’s March -22 showdown with the government over a motion to compel it to help -hack the iPhone used by one of the San Bernardino terrorists.

-
- -

For what it’s worth, last year’s March event was on a Monday as well.

- -

Update: Jim Dalrymple:

- -
-

This sounds right to me.

-
- -
- - -
- - -
-
-Manuscripts and Findings  -
-
-

My thanks to Nucleobytes for sponsoring this week’s DF RSS feed. Nucleobytes is a fascinating company. They specialize in creating Mac and iOS software for scientists and researchers, and they do it with great style — their apps have won multiple Apple Design Awards.

- -

Their latest creations are two apps for researchers, useful for anyone who researches anything from lab results, cooking recipes, or research for blog posts: Manuscripts and Findings.

- -
    -
  • Manuscripts is a writing tool that helps you concentrate on your story. Outline, plan and edit your project, insert figures, tables and math, then format citations using a killer workflow. Manuscripts supports both importing and exporting Markdown, Word, LaTeX, and HTML.

  • -
  • Findings is a lab notebook app that helps you keep a journal of your research, connected to notes, photos, and files. Plan your week, track progress, and share your findings with your colleagues or the world.

  • -
- -

Try the free basic versions, and use coupon DARINGFIREBALL for a special discount on the unlimited versions, this week only. (They have an even better offer for students.)

- -
- - -
-Donald Trump Vows to ‘Open Up’ Libel Laws  -
-
-

Hadas Gold, writing for Politico:

- -
-

During a rally in Fort Worth, Texas, Trump began his usual tirade -against newspapers such as The New York Times and The Washington -Post, saying they’re “losing money” and are “dishonest.” The -Republican presidential candidate then took a different turn, -suggesting that when he’s president they’ll “have problems.”

- -

“One of the things I’m going to do if I win, and I hope we do and -we’re certainly leading. I’m going to open up our libel laws so -when they write purposely negative and horrible and false -articles, we can sue them and win lots of money. We’re going to -open up those libel laws. So when The New York Times writes a hit -piece which is a total disgrace or when The Washington Post, which -is there for other reasons, writes a hit piece, we can sue them -and win money instead of having no chance of winning because -they’re totally protected,” Trump said.

-
- -

Not worrisome at all. No sir.

- -
- - -
-Most Android Phones Are Not Encrypted  -
-
-

Jose Pagliery, writing for CNN Money:

- -
-

Although 97% of Android phones have encryption as an option, less -than 35% of them actually got prompted to turn it on when they -first activated the phone. Even then, not everybody chooses that -extra layer of security.

- -

A Google spokesman said that encryption is now required for all -“high-performing devices” — like the Galaxy S7 — running the -latest version of Android, Marshmallow. But only 1.2% of Android -phones even have that version, according to Google.

- -

By comparison, most Apple products are uniformly secure: 94% of -iPhones run iOS 8 or 9, which encrypt all data. Apple (AAPL, -Tech30) makes its devices, designs the software, and retains full -control of the phone’s operating system.

- -

“If a person walks into a Best Buy and walks out with an iPhone, -it’s encrypted by default. If they walk out with an Android phone, -it’s largely vulnerable to surveillance,” said Christopher -Soghoian, the principal technologist at the American Civil -Liberties Union.

-
- -

Google is moving in the right direction, but here’s an area where the slow uptake of new versions of Android has a serious effect.

- -
- - -
-9to5Mac: ‘Apple Likely to Drop the “5”, Call New 4-Inch Model the “iPhone SE”’  -
-
-

Mark Gurman:

- -
-

In January, we reported that Apple is preparing a new 4-inch -iPhone that is essentially 2013’s iPhone 5s with upgraded -internals. At the time, we heard that Apple would call the device -the “iPhone 5se” based on it being both an enhanced and “special -edition” version of the iPhone 5s. Now, we are hearing that Apple -appears to be going all in on the special edition factor: sources -say that Apple has decided to drop the “5” from the device’s name -and simply call it the “iPhone SE.” This will mark the first -iPhone upgrade without a number in its name and would logically -remove it from a yearly update cycle.

-
- -

A few points:

- -
    -
  • Apple was never going to call this phone the “5 SE”. I don’t know where Gurman got that, but that was never going to happen. Why would Apple give a new phone a name that makes it sound old?

  • -
  • Isn’t it more accurate to think of this as an iPhone 6S in a 4-inch body than as an iPhone 5S with “upgraded internals”? Other than the display, aren’t the “internals” the defining characteristics of any iPhone?

  • -
  • Dropping the number entirely fits with my theory that this phone is intended to remain on the market for 18-24 months.

  • -
- -
- - -
-Gogo Wi-Fi and Email Security  -
-
-

Reporter Steven Petrow published a scary first-hand tale in USA Today, claiming that his email was hacked by another passenger on a Gogo-enabled flight. The implication was that you shouldn’t use email on Gogo unless you’re using a VPN.

- -

But Petrow’s email didn’t get intercepted because of some flaw with Gogo. It got intercepted because he wasn’t connecting to the POP or SMTP servers via SSL. In fact, his email provider, Earthlink, doesn’t even support SSL for email.

- -

Robert Graham at Errata Security explains:

- -
-

Early Internet stuff wasn’t encrypted, because encryption was -hard, and it was hard for bad guys to tap into wires to eavesdrop. -Now, with open WiFi hotspots at Starbucks or on the airplane, it’s -easy for hackers to eavesdrop on your network traffic. -Simultaneously, encryption has become a lot easier. All new -companies, those still fighting to acquire new customers, have -thus upgraded their infrastructure to support encryption. Stagnant -old companies, who are just milking their customers for profits, -haven’t upgraded their infrastructure.

- -

You see this in the picture below. Earthlink supports older -un-encrypted “POP3” (for fetching email from the server), but not -the new encrypted POP3 over SSL. Conversely, GMail doesn’t support -the older un-encrypted stuff (even if you wanted it to), but only -the newer encrypted version.

-
- -

Gogo is far from perfect, but it certainly wasn’t at fault in this case.

- -

Update: Like a lot of you, I’m not even sure I buy the whole story. Whole thing seems fishy.

- -
- - -
- - -
-
-Google, Facebook, Twitter, and Microsoft Plan to Support Apple  -
-
-

Deepa Seetharaman and Jack Nicas, reporting for the WSJ:

- -
-

Several tech companies, including Google parent Alphabet Inc., -Facebook Inc. and Microsoft Corp., plan to file a joint motion -supporting Apple Inc. in its court fight against the Justice -Department over unlocking an alleged terrorist’s iPhone, according -to people familiar with the companies’ plans.

- -

At least one other tech company plans to be included in a joint -amicus brief next week generally supporting Apple’s position that -unlocking the iPhone would undermine tech firms’ efforts to -protect their users’ digital security, these people said. Twitter -Inc. also plans to support Apple in a motion, though it is unclear -if it will join the combined filing, another person familiar said.

- -

Microsoft President and Chief Legal Officer Brad Smith told -Congress on Thursday that his company would file a motion -supporting Apple.

-
- -

Nice.

- -
- - -
-Apple’s Motion to Vacate FBI Order  -
-
-

A clear, cogent read. I often shy away from reading legal motions because they’re so often written in dense legalese, but this one is clear.

- -

This stuck out to me:

- -
-

Congress knows how to impose a duty on third parties to facilitate -the government’s decryption of devices. Similarly, it knows -exactly how to place limits on what the government can require of -telecommunications carriers and also on manufacturers of telephone -equipment and handsets. And in CALEA, Congress decided not to -require electronic communication service providers, like Apple, to -do what the government seeks here. Contrary to the government’s -contention that CALEA is inapplicable to this dispute, Congress -declared via CALEA that the government cannot dictate to providers -of electronic communications services or manufacturers of -telecommunications equipment any specific equipment design or -software configuration.

- -

In the section of CALEA entitled “Design of features and systems -configurations,” 47 U.S.C. § 1002(b)(1), the statute says that it -“does not authorize any law enforcement agency or officer —

- -
-

(1) to require any specific design of equipment, facilities, - services, features, or system configurations to be adopted by - any provider of a wire or electronic communication service, - any manufacturer of telecommunications equipment, or any - provider of telecommunications support services.

- -

(2) to prohibit the adoption of any equipment, facility, service, - or feature by any provider of a wire or electronic - communication service, any manufacturer of telecommunications - equipment, or any provider of telecommunications support - services.

-
-
- -

What Apple is arguing is that the All Writs Act is intended only to fill the gaps covering scenarios not covered by other laws, but CALEA (the Communications Assistance for Law Enforcement Act) is a law that was passed specifically to cover exactly this sort of scenario. This strikes me as a very compelling argument.

- -
- - -
-Microsoft Will File Amicus Brief Supporting Apple  -
-
-

Dina Bass, reporting for Bloomberg:

- -
-

Microsoft Corp. backs Apple Inc. in its fight with the U.S. -government over unlocking a terrorist’s iPhone, said President and -Chief Legal Officer Brad Smith.

- -

The company will file an amicus brief to support Apple next week, -Smith said at a congressional hearing to discuss the need for new -legislation to govern privacy, security and law enforcement in the -age of Internet-based cloud services.

-
- -

Nice.

- -
- - -
-Apple to Tighten iCloud Backup Encryption  -
-
-

Tim Bradshaw, reporting for the Financial Times:

- -
-

Apple is working on new ways to strengthen the encryption of -customers’ iCloud backups in a way that would make it impossible -for the company to comply with valid requests for data from law -enforcement, according to people familiar with its plans.

- -

The move would bolster Apple customers’ security against hackers -but also frustrate investigators who are currently able to obtain -data from Apple’s servers through a court order. Apple has -complied with thousands of such orders in the past.

- -

Developing such technology is in some ways more complex than -adding the kind of device-level security that Apple introduced to -the iPhone in 2014 with its iOS 8 update.

- -

Building new protections that mean Apple no longer has access to -iCloud encryption keys may inconvenience some customers. Such a -change would most likely mean that customers who forget their -iCloud password may be left unable to access their photos, -contacts and other personal information that is backed up to -Apple’s systems.

-
- -
- - -
-The Dangerous All Writs Act Precedent in the Apple Encryption Case  -
-
-

Amy Davidson, writing for The New Yorker:

- -
-

It is essential to this story that the order to Apple is not a -subpoena: it is issued under the All Writs Act of 1789, which says -that federal courts can issue “all writs necessary or appropriate -in aid of their respective jurisdictions and agreeable to the -usages and principles of law.” Read as a whole, this simply means -that judges can tell people to follow the law, but they have to do -so in a way that, in itself, respects the law. The Act was written -at a time when a lot of the mechanics of the law still had to be -worked out. But there are qualifications there: warnings about the -writs having to be “appropriate” and “agreeable,” not just to the -law but to the law’s “principles.” The government, in its use of -the writ now, seems to be treating those caveats as background -noise. If it can tell Apple, which has been accused of no -wrongdoing, to sit down and write a custom operating system for -it, what else could it do?

-
- -

Lost amid the technical debate over encryption is the legal debate over this incredibly broad application of the All Writs Act.

- -
- - -
-Twitter’s Missing Manual  -
-
-

Eevee:

- -
-

Here, then, is a list of all the non-obvious things about Twitter -that I know. Consider it both a reference for people who aren’t up -to their eyeballs in Twitter, and an example of how these hidden -features can pile up. I’m also throwing in a couple notes on -etiquette, because I think that’s strongly informed by the shape -of the platform.

-
- -
- - -
-Sharp Accepts Foxconn Takeover Bid  -
-
-

Huge news for both companies. Interesting for Apple, too.

- -

Update:

- -
-

A deal to take over Japanese electronics giant Sharp by Taiwanese -manufacturer Foxconn, has been thrown into question by a last -minute delay.

- -

Foxconn said it had received new information from Sharp which -needed to be clarified.

-
- -

Whoops.

- -
- - -
- -
- - -
-

On the San Bernardino Suspect’s Apple ID Password Reset

- - -

The latest news in the Apple-FBI legal fight has resulted in much confusion. John Paczkowski, reporting for BuzzFeed:

- -
-

The FBI has claimed that the password was changed by someone at -the San Bernardino Health Department. Friday night, however, -things took a further turn when the San Bernardino County’s -official Twitter account stated, “The County was working -cooperatively with the FBI when it reset the iCloud password at -the FBI’s request.”

- -

County spokesman David Wert told BuzzFeed News on Saturday -afternoon the tweet was an authentic statement, but he had nothing -further to add.

- -

The Justice Department did not respond to requests for comment on -Saturday; an Apple spokesperson said the company had no additional -comment beyond prior statements.

-
- -

Here is what the FBI wrote in its legal motion, in a footnote on the four ways Apple suggested they obtain the data they seek:

- -
-

(3) to attempt an auto-backup of the SUBJECT DEVICE with the - related iCloud account (which would not work in this case - because neither the owner nor the government knew the password - the iCloud account, and the owner, in an attempt to gain - access to some information in the hours after the attack, was - able to reset the password remotely, but that had the effect - of eliminating the possibility of an auto-backup);

-
- -

To unpack this, the “owner” is not Syed Farook, the shooter. The iPhone at the center of this was supplied by Farook’s employer, the San Bernardino County Department of Public Health. They are the “owner”. The “government” is the federal government: the FBI and the Department of Justice.

- -

The iPhone had been configured to back up to iCloud. However, at the time of the attack, it had not been backed up to iCloud for six weeks. Under warrant, Apple supplied the FBI with the data from that six-week-old backup. The FBI (for obvious reasons) would like the most recent six weeks of data from the phone, too.1

- -

iCloud backups are triggered automatically when the phone is (a) on a known Wi-Fi network, and (b) plugged-in to power. Apple’s suggestion to the FBI was that if they took the iPhone to Farook’s office and plugged it in, it might trigger a backup. If that had worked, Apple could supply the FBI with the contents of that new backup, including the most recent six weeks of data.

- -

It is not clear to me from any of the reports I have read why the iPhone had not been backed up in six weeks. It’s possible that Farook had disabled iCloud backups, in which case this whole thing is moot.2 But it’s also possible the only reason the phone hadn’t been backed up in six weeks is that it had not been plugged-in while on a known Wi-Fi network in six weeks. The phone would have to be unlocked to determine this, and the whole point of this fight is that the phone can’t be unlocked.

- -

The FBI screwed this up by directing the San Bernardino County Department of Public Health to reset Farook’s Apple ID password. They did not, and apparently could not, change anything on the phone itself. But once they reset the Apple ID password, the phone could not back up to iCloud, because the phone needed to be updated with the newly-reset Apple ID password — and they could not do that because they can’t unlock the phone.

- -

The key point is that you do not have to unlock an iPhone to have it back up to iCloud. But a locked iPhone can’t back up to iCloud if the associated Apple ID password has been changed.

- -

Again, there are two password-type things at play here. The Apple ID (iCloud) password, and the four-digit device passcode locking the iPhone. The county, at the behest of the FBI, reset the Apple ID password. This did not allow them to unlock the iPhone, and, worse, it prevented the iPhone from initiating a new backup to iCloud.

- -

How did the county reset Farook’s Apple ID password? We don’t know for sure, but the most likely answer is that if his Apple ID was his work-issued email account, then the IT department at the county could go to iforgot.apple.com, enter Farook’s work email address, and then access his email account to click the confirmation URL to reset the password.

- -

In short:

- -
    -
  • The data the FBI claims to want is on Farook’s iPhone.
  • -
  • They already have access to his iCloud account.
  • -
  • They might have been able to transfer the data on his iPhone to his iCloud account via an automated backup, but they can’t because they reset his Apple ID (iCloud) password.
  • -
- -

The only possible explanations for this are incompetence or dishonesty on the part of the FBI. Incompetence, if they didn’t realize that resetting the Apple ID password could prevent the iPhone from backing up to iCloud. Dishonesty, if they directed the county to do this knowing the repercussions, with the goal of setting up this fight to force Apple to create a back door for them in iOS. I’m not sure which to believe at this point. I’d like to know exactly when this directive to reset the Apple ID password was given — ” in the hours after the attack” leaves a lot of wiggle room. 

- -
-
-
    -
  1. -

    Much (or all?) of the data stored on Apple’s iCloud backup servers is not encrypted. Or, if it is encrypted, it is encrypted in a way that Apple can decrypt. Apple has a PDF that describes the information available to U.S. law enforcement from iCloud, but to me it’s not clear exactly what is available under warrant. I would bet a large sum of money that Apple is hard at work on an iCloud backup system that does store data encrypted in a way that Apple cannot read it without the user’s Apple ID password. ↩︎

    -
  2. -
  3. -

    Another possibility: Farook’s iCloud storage was full. If this were the case, presumably Apple could have granted his account additional storage to allow a fresh backup to occur. But again, this became moot as soon as the county reset the Apple ID password at the behest of the FBI. ↩︎︎

    -
  4. -
-
- -
- -
- - -
-

Apple’s App Problem

- - -

Following up on Walt Mossberg’s column regarding the quality of Apple’s first-party apps, Jim Dalrymple writes:

- -
-

I understand that Apple has a lot of balls in the air, but they -have clearly taken their eye off some of them. There is absolutely -no doubt that Apple Music is getting better with each update to -the app, but what we have now is more of a 1.0 version than what -we received last year.

- -

Personally, I don’t care much about all the celebrities that Apple -can parade around — I care about a music service that works. -That’s it.

- -

If Apple Music (or any of the other software that has -problems) was the iPhone, it would never have been released in -the state it was.

-
- -

Software and hardware are profoundly different disciplines, so it’s hard to compare them directly. But it seems obvious to me that Apple, institutionally, has higher standards for hardware design and quality than it does for software.

- -

Maybe this is the natural result of the fact hardware standards must be high, because they can’t issue “hardware updates” over the air like they can with software. But the perception is now widespread that the balance between Apple’s hardware and software quality has shifted in recent years. I see a lot of people nodding their heads in agreement with Mossberg and Dalrymple’s pieces today.

- -

We went over this same ground a year ago in the wake of Marco Arment’s “Apple Has Lost the Functional High Ground”, culminating in a really interesting (to me at least) discussion with Phil Schiller at my “Live From WWDC” episode of The Talk Show. That we’re still talking about it a year later — and that the consensus reaction is one of agreement — suggests that Apple probably does have a software problem, and they definitely have a perception problem.

- -

I’ll offer a small personal anecdote. Overall I’ve had great success with iCloud Photo Library. I’ve got over 18,000 photos and almost 400 videos. And I’ve got a slew of devices — iPhones, iPads, and Macs — all using the same iCloud account. And those photos are available from all those devices. Except, a few weeks ago, I noticed that on my primary Mac, in Photos, at the bottom of the main “Photos” view, where it tells you exactly how many photos and videos you have, it said “Unable to Upload 5 Items”. Restarting didn’t fix it. Waiting didn’t fix it. And clicking on it didn’t do anything — I wanted to know which five items couldn’t be uploaded, and why. It seems to me that anybody in this situation would want to know those two things. But damned if Photos would tell me.

- -

Eventually, I found this support thread which suggested a solution: you can create a Smart Group in Photos using “Unable to upload to iCloud Photo Library” as the matching condition. Bingo: five items showed up. (Two of them were videos for which the original files couldn’t be found; three of them were duplicates of photos that were already in my library.)

- -

My little iCloud Photo Library syncing hiccup was not a huge deal — I was even lucky insofar as the two videos that couldn’t be found were meaningless. And I managed to find a solution. But it feels emblematic of the sort of nagging software problems people are struggling with in Apple’s apps. Not even the bug itself that led to these five items being unable to upload, but rather the fact that Photos knew about the problem but wouldn’t tell me the details I needed to fix it without my resorting to the very much non-obvious trick of creating a Smart Group to identify them. For me at least, “silent failure” is a big part of the problem — almost everything related to the whole discoveryd/mDNSresponder fiasco last year was about things that just silently stopped working.

- -

Maybe we expect too much from Apple’s software. But Apple’s hardware doesn’t have little problems like this. 

- -
- -
- - -
-

Why Apple Assembles in China

- - -

Arik Hesseldahl, writing for Recode on Donald Trump’s “we’re gonna get Apple to start building their damn computers and things in this country, instead of in other countries” campaign promise:

- -
-

Any honest presidential candidate regardless of party should say -clearly and indeed proudly that America doesn’t want these jobs to -come back. Final assembly jobs are low-skilled, low-paying -occupations; no American would wish to support a family on what -the jobs would pay. Workers at China’s Foxconn, which -manufacturers the iPhone, make about $402 per month after three -months of on-the-job probation. Even at the lowest minimum wage in -the U.S. — $5.15 an hour in Wyoming — American workers can’t -beat that.

-
- -

It’s not that simple. These jobs are certainly menial, but they’re not low-skill. As Tim Cook said on 60 Minutes:

- -
-

Charlie Rose: So if it’s not wages, what is it?

- -

Tim Cook: It’s skill. […]

- -

Charlie Rose: They have more skills than American workers? They -have more skills than —

- -

Tim Cook: Now — now, hold on.

- -

Charlie Rose: — German workers?

- -

Tim Cook: Yeah, let me — let me — let me clear, China put an -enormous focus on manufacturing. In what we would call, you and I -would call vocational kind of skills. The U.S., over time, began -to stop having as many vocational kind of skills. I mean, you can -take every tool and die maker in the United States and probably -put them in a room that we’re currently sitting in. In China, you -would have to have multiple football fields.

- -

Charlie Rose: Because they’ve taught those skills in their -schools?

- -

Tim Cook: It’s because it was a focus of them — it’s a focus of -their educational system. And so that is the reality.

-
- -

Wages are a huge factor, but for the sake of argument, let’s say Apple was willing to dip into its massive cash reserves and pay assembly line workers in the U.S. a good wage. Where would these U.S.-made iPhone be assembled? A year ago Apple sold 75 million iPhones in the fourth quarter of calendar 2014. There is no facility in the U.S. that can do that. There might not be anywhere in the world other than China that can operate at that sort of scale. That’s almost one million iPhones per day. 10 iPhones per second. Think about that.

- -

You can say, well, Apple could dig even deeper into its coffers and build such facilities. And train tens of thousands of employees. But why would they? Part of the marvel of Apple’s operations is that they can assemble and sell an unfathomable number of devices but they’re not on the hook for the assembly plants and facilities. When iPhones go the way of the iPod in 10 or 15 or 20 years, Apple doesn’t have any factories to close or convert for other uses. Foxconn does.

- -

The U.S. can’t compete with China on wages. It can’t compete on the size of the labor force. China has had a decades-long push in its education system to train these workers; the U.S. has not. And the U.S. doesn’t have the facilities or the proximity to the Asian component manufacturers.

- -

The only way Apple could ever switch to U.S. assembly and manufacturing would be if they automated the entire process — to build machines that build the machines. That, in fact, is what NeXT did while they were in the hardware business. But NeXT only ever sold about 50,000 computers total. Apple needed to assemble 35,000 iPhones per hour last year.

- -

So long as assembling these devices remains labor intensive, it has to happen in China. And if someday it becomes automated — if the machines are built by machines — by definition it’s not going to create manufacturing jobs.1 

- -
-
-
    -
  1. -

    I do wonder about the purported Apple car. Would that be assembled in China, too? The U.S. does have automobile manufacturing expertise. And a car is so utterly unlike any product Apple has ever made that I feel like anything is possible. ↩︎

    -
  2. -
-
- -
- -
- - - - -
- - -

-Ads via The Deck -Ads via The Deck -

- -
- - - - - - -
- -
- - - - diff --git a/RSParser/Tests/RSParserTests/Resources/DaringFireball.json b/RSParser/Tests/RSParserTests/Resources/DaringFireball.json deleted file mode 100644 index 613269e88..000000000 --- a/RSParser/Tests/RSParserTests/Resources/DaringFireball.json +++ /dev/null @@ -1,584 +0,0 @@ -{ - "version" : "https://jsonfeed.org/version/1", - "title" : "Daring Fireball", - "home_page_url" : "https://daringfireball.net/", - "feed_url" : "https://daringfireball.net/feeds/json", - "author" : { - "url" : "https://twitter.com/gruber", - "name" : "John Gruber" - }, - "icon" : "https://daringfireball.net/graphics/apple-touch-icon.png", - "favicon" : "https://daringfireball.net/graphics/favicon-64.png", - "items" : [ - { - "title" : "The Talk Show: ‘I Do Like Throwing a Baby’", - "date_published" : "2017-06-27T00:54:17Z", - "date_modified" : "2017-06-27T00:54:20Z", - "id" : "https://daringfireball.net/linked/2017/06/26/the-talk-show-195", - "url" : "https://daringfireball.net/linked/2017/06/26/the-talk-show-195", - "external_url" : "https://daringfireball.net/thetalkshow/2017/06/26/ep-195", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

New episode of America’s favorite 3-star podcast, with special guest John Moltz. Topics include more follow-up from WWDC 2017, the iPad Pro models and ProMotion, Scott Forstall’s interview with John Markoff regarding the 10-year anniversary of the original iPhone, the ongoing shitshow at Uber, quick thoughts on the Nintendo Switch, and more. Also: guess which John enjoys throwing babies into the air.

\n\n

Sponsored by:

\n\n
    \n
  • Squarespace: Make your next move. Use code GRUBER for 10% off your first order.
  • \n
  • Away: High-quality luggage with built-in USB chargers. Save $20 with promo code TALKSHOW.
  • \n
  • MailRoute: Hosted spam and virus protection for email. Use this link for 10% off for the life of your account.
  • \n
\n\n\n\n\t" - }, - { - "title" : "Virgin Mobile Partners With Apple to Go iPhone-Only With $1 Service", - "date_published" : "2017-06-26T23:51:11Z", - "date_modified" : "2017-06-26T23:51:13Z", - "id" : "https://daringfireball.net/linked/2017/06/26/virgin-mobile-iphone-only", - "url" : "https://daringfireball.net/linked/2017/06/26/virgin-mobile-iphone-only", - "external_url" : "http://tidbits.com/article/17308", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Josh Centers, writing for TidBITS on Virgin Mobile’s intriguing decision to go iPhone-only:

\n\n
\n

Pundits have long suspected that two roadblocks stood in the way\nof Apple becoming a carrier: the infrastructure is incredibly\nexpensive, even if you lease it from the larger carriers, and\nApple could limit the iPhone business if it were to compete with\nthe major carriers.

\n\n

But Apple has sidestepped those concerns by essentially taking\nover a carrier (actually a carrier-owned MVNO — Mobile Virtual\nNetwork Operator) without acquiring it. Apple may not own Virgin\nMobile, but Virgin Mobile is now utterly dependent on Apple and\nwill benefit through promotion in Apple Stores.

\n\n

We shouldn’t read too much into this deal, but at the very least\nit’s unusual to see a company like Virgin Mobile going all-in on\nthe iPhone. And it might point toward Apple dipping its toe into\nthe MVNO business.

\n
\n\n

Virgin Mobile is owned by Sprint (and thus uses Sprint’s back-end), and in my experience Sprint is the worst of the U.S. carriers, so this is not a panacea. But it is intriguing.

\n\n\n\n\t" - }, - { - "title" : "The Verge: ‘Apple’s AR Is Closer to Reality Than Google’s’", - "date_published" : "2017-06-26T23:02:21Z", - "date_modified" : "2017-06-26T23:02:23Z", - "id" : "https://daringfireball.net/linked/2017/06/26/arkit-the-verge", - "url" : "https://daringfireball.net/linked/2017/06/26/arkit-the-verge", - "external_url" : "https://www.theverge.com/2017/6/26/15872332/apple-arkit-ios-11-augmented-reality-developer-excitement", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Two great examples via the very fun Made With ARKit Twitter account: here and here.

\n\n\n\n\t" - }, - { - "title" : "Rene Ritchie’s First Look at the iOS 11 Public Beta", - "date_published" : "2017-06-26T22:55:13Z", - "date_modified" : "2017-06-26T23:13:17Z", - "id" : "https://daringfireball.net/linked/2017/06/26/ios-11-public-beta-ritchie", - "url" : "https://daringfireball.net/linked/2017/06/26/ios-11-public-beta-ritchie", - "external_url" : "https://www.imore.com/ios-11", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Rene Ritchie has a comprehensive look at the just-released public beta of iOS 11. Romain Dillet has a good preview at TechCrunch too. The gist of both previews: it’s the “I hope Apple truly focuses on the iPad this year” release of iOS that we’ve been waiting for.

\n\n

I’ve been using the developer betas on my 10.5-inch iPad Pro review unit and a spare iPhone. I’m willing to wait to install iOS 11 on my primary iPhone, but at this point, bugs be damned, I wouldn’t want to use an iPad running iOS 10.3. It’s stable enough, and the benefits of the great new features for iPad far outweigh the downsides of the beta (which, in addition to crashing bugs, include questionable battery life).

\n\n\n\n\t" - }, - { - "title" : "Amazon’s New Echo Show Is Very Cool and a Little Creepy", - "date_published" : "2017-06-26T22:08:09Z", - "date_modified" : "2017-06-26T23:18:49Z", - "id" : "https://daringfireball.net/linked/2017/06/26/echo-show-creep", - "url" : "https://daringfireball.net/linked/2017/06/26/echo-show-creep", - "external_url" : "https://www.buzzfeed.com/mathonan/meet-amazons-new-echo-show-alexa-is-watching", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Mat Honan, writing for BuzzFeed:

\n\n
\n

It has this wild new feature called Drop In. Drop In lets you give\npeople permission to automatically connect with your device.\nHere’s how it works. Let’s say my father has activated Drop In for\nme on his Echo Show. All I have to do is say, “Alexa, drop in on\nDad.” It then turns on the microphone and camera on my father’s\ndevice and starts broadcasting that to me. For the several seconds\nof the call, my father’s video screen would appear fogged over.\nBut then there he’ll be. And to be clear: This happens even if he\ndoesn’t answer. Unless he declines the call, audibly or by tapping\non the screen, it goes through. It just starts. Hello, you look\nnice today.

\n\n

Honestly, I haven’t figured out what to think about this yet. But,\nit’s here.

\n
\n\n

I know what to think of this: No fucking way do I want this.

\n\n

Update: I’ve already gotten a few reader responses arguing that this feature could be great for an Echo Show in the home of an elderly relative. You visit and set it up in their house, explain to them what it does, and then you can check in with them without their needing to do anything at all. I can see that. You can think of it as the digital equivalent of having a set of keys to someone’s house — something you’d only grant to a deeply trusted friend or loved one.

\n\n\n\n\t" - }, - { - "title" : "Verizon to Block Email Addresses From Rival Carriers From Logging Into Yahoo or Tumblr Accounts", - "date_published" : "2017-06-26T21:18:46Z", - "date_modified" : "2017-06-26T22:18:03Z", - "id" : "https://daringfireball.net/linked/2017/06/26/verizon-fuck-heads", - "url" : "https://daringfireball.net/linked/2017/06/26/verizon-fuck-heads", - "external_url" : "https://tumblr.zendesk.com/hc/en-us/articles/115007729788-Heads-up-for-AT-T-customers", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

From a Tumblr help document euphemistically titled “Heads-Up for AT&T Customers”:

\n\n
\n

Starting on June 30, 2017, att.net customers will no longer be\nable to log in to their Yahoo and Tumblr accounts through email\naddresses with the following domains: att.net, ameritech.net,\nbellsouth.net, flash.net, nvbell.net, pacbell.net, prodigy.net,\nsbcglobal.net, snet.net, swbell.net, and wans.net.

\n
\n\n

The sheer egregiousness of this is outrageous on its face, but it’s even worse when you consider that Tumblr, when it was independent, was a champion for net neutrality.

\n\n

Update: TechCrunch says it’s just a deal expiring, not spite:

\n\n
\n

As part of the new corporate merger of Yahoo and Aol under the\nOath brand, it looks like Yahoo accounts will no longer be\naccessible through AT&T email addresses (or those of any A&T\nsubsidiaries).

\n\n

The move provoked some uproar among net neutrality advocates, but\nit seems to be less about creating walled gardens and more about\ncleaning up prior commitments and pre-existing partnerships.\nWhile there is a level of inconvenience for AT&T customers, this\nis less about net neutrality and more about unwinding those\ncorporate deals.

\n
\n\n

I still say fuck Verizon and their stance on net neutrality.

\n\n\n\n\t" - }, - { - "title" : "Trump’s Lies, the Definitive List", - "date_published" : "2017-06-26T21:08:43Z", - "date_modified" : "2017-06-26T21:08:44Z", - "id" : "https://daringfireball.net/linked/2017/06/26/trumps-lies", - "url" : "https://daringfireball.net/linked/2017/06/26/trumps-lies", - "external_url" : "https://www.nytimes.com/interactive/2017/06/23/opinion/trumps-lies.html?_r=0", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Copiously documented and perfectly presented. Looked striking in the print edition, too.

\n\n\n\n\t" - }, - { - "title" : "[Sponsor] Timing — Automatic Time Tracking for Mac", - "date_published" : "2017-06-26T19:22:44-04:00", - "date_modified" : "2017-06-26T19:22:46-04:00", - "id" : "https://daringfireball.net/feeds/sponsors/2017/06/timing_automatic_time_tracking", - "url" : "https://daringfireball.net/feeds/sponsors/2017/06/timing_automatic_time_tracking", - "external_url" : "https://timingapp.com/df", - "author" : { - "name" : "Daring Fireball Department of Commerce" - }, - "content_html" : "\n

Time is your most precious resource. You need to know how you are spending it.
\nBut time tracking sucks. Big Time. (Pun intended.)

\n\n

The brand new Timing fixes that. It automatically tracks which apps, documents and websites you use — without start/stop timers. See how you spend your time, eliminate distracting activities, and improve your client billing. Mind you, this data is super sensitive, so Timing keeps it safe on your Mac.

\n\n

Stop worrying about time and focus on doing your best work instead.

\n\n

Download a free 14-day trial today and get 10 percent off through next Monday.

\n\n\n\n\t" - }, - { - "title" : "Delta Updates and App Thinning Do Not Solve the Apps-Are-Too-Damn-Big Problem", - "date_published" : "2017-06-25T15:31:10Z", - "date_modified" : "2017-06-25T15:31:12Z", - "id" : "https://daringfireball.net/linked/2017/06/25/delta-updates-birchler", - "url" : "https://daringfireball.net/linked/2017/06/25/delta-updates-birchler", - "external_url" : "https://birchtree.me/blog/the-apps-are-too-damn-big/", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Matt Birchler:

\n\n
\n

“App thinning” is not a magic bullet that erases this problem\nthough, as Facebook Messenger, which shows as being 154 MB, still\ndownloaded 99 MB of data for its update. […]

\n\n

So are giant app sizes a problem? Yes. Do delta updates allow\nthese updates to use less data? Yes. Do delta updates make these\nlarge apps a non-issue? Hell no!

\n
\n\n\n\n\t" - }, - { - "title" : "Mnml – A Mac Client for Medium", - "date_published" : "2017-06-25T00:14:25Z", - "date_modified" : "2017-06-25T02:42:27Z", - "id" : "https://daringfireball.net/linked/2017/06/24/mnml", - "url" : "https://daringfireball.net/linked/2017/06/24/mnml", - "external_url" : "http://mnml.af/", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

My thanks to Mnml for sponsoring this week’s DF RSS feed. It’s a native Mac app client for Medium, and can be used for writing, blogging, and notetaking. Based upon the same engine that twice won Desk “Best Apps of the Year” honors, Mnml has all the features you’ll need, wrapped in an attractive, fun – and yes, minimal – interface. Anyone who writes for Medium and prefers native client apps should check it out.

\n\n\n\n\t" - }, - { - "title" : "Scott Galloway on Amazon and Whole Foods", - "date_published" : "2017-06-23T18:45:50Z", - "date_modified" : "2017-06-23T19:23:34Z", - "id" : "https://daringfireball.net/linked/2017/06/23/galloway-amzn-wfm", - "url" : "https://daringfireball.net/linked/2017/06/23/galloway-amzn-wfm", - "external_url" : "https://www.l2inc.com/daily-insights/no-mercy-no-malice/amzn-wfm-1t", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Scott Galloway:

\n\n
\n

Amazon / Whole Foods will be the fourth-largest grocer in the US,\nand will likely post growth rates no $10B+ retailer, sans Amazon,\nhas registered. The Seattle firm will apply its operational chops\nand lower (zero) profit hurdle to the Whole Foods business model\nand bring prices (way) down. If you wish you could shop at Whole\nFoods more often, but it’s too expensive, your prayers have been\nanswered. Whole Foods will become the grocery equivalent of a\nMercedes for the price of a Toyota. Grocery has stuck their chin\nout (little innovation), and the entire sector is about to have\nits jaw shattered.

\n
\n\n

It’s a great piece. I disagree with him on this though:

\n\n
\n

Amazon will displace Apple as the top tech hardware innovator,\nwith Alexa cementing itself as the gadget that defines the decade\n(post iPhone). Grocery / commerce via Alexa will create the\nutility that Alexa needs to [maintain its lead] over Google and\nApple’s home / voice offerings as they try to play catch-up.

\n
\n\n

Alexa may well maintain its lead in the smart speaker market. It may even grow. Maybe HomePod will be a complete bust. But even if all of that happens, the smartphone will remain the dominant device in people’s lives. Something will eventually replace the phone, but smart speakers aren’t it.

\n\n

Hardware just isn’t where Amazon is good.

\n\n\n\n\t" - }, - { - "title" : "Google Will Stop Reading Your Emails for Gmail Ads", - "date_published" : "2017-06-23T18:32:03Z", - "date_modified" : "2017-06-23T18:32:05Z", - "id" : "https://daringfireball.net/linked/2017/06/23/gmail-privacy", - "url" : "https://daringfireball.net/linked/2017/06/23/gmail-privacy", - "external_url" : "https://www.bloomberg.com/news/articles/2017-06-23/google-will-stop-reading-your-emails-for-gmail-ads", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Mark Bergen, reporting for Bloomberg:

\n\n
\n

Google is stopping one of the most controversial advertising\nformats: ads inside Gmail that scan users’ email contents. The\ndecision didn’t come from Google’s ad team, but from its cloud\nunit, which is angling to sign up more corporate customers.

\n\n

Alphabet Inc.’s Google Cloud sells a package of office software,\ncalled G Suite, that competes with market leader Microsoft Corp.\nPaying Gmail users never received the email-scanning ads like the\nfree version of the program, but some business customers were\nconfused by the distinction and its privacy implications, said\nDiane Greene, Google’s senior vice president of cloud. “What we’re\ngoing to do is make it unambiguous,” she said.

\n
\n\n

This is terrific news. Not just because it’s a good policy change in and of itself, but I take it as a sign that Google’s leadership is starting to realize how much damage they’ve done to the company’s reputation by playing fast and loose with their users’ privacy.

\n\n\n\n\t" - }, - { - "title" : "Who Americans Spend Their Time With", - "date_published" : "2017-06-23T18:13:55Z", - "date_modified" : "2017-06-23T18:15:59Z", - "id" : "https://daringfireball.net/linked/2017/06/23/who-americans-spend-their-time-with", - "url" : "https://daringfireball.net/linked/2017/06/23/who-americans-spend-their-time-with", - "external_url" : "https://www.theatlas.com/charts/HJFYm4uQ-", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Via Jim Coudal, who summarizes this perfectly: “Poetry, in data”.

\n\n\n\n\t" - }, - { - "title" : "Kevin Drum’s Thoughtful Critique of Trumpcare", - "date_published" : "2017-06-23T17:58:25Z", - "date_modified" : "2017-06-23T17:58:27Z", - "id" : "https://daringfireball.net/linked/2017/06/23/drum-trumpcare", - "url" : "https://daringfireball.net/linked/2017/06/23/drum-trumpcare", - "external_url" : "http://www.motherjones.com/kevin-drum/2017/06/my-thoughtful-critique-of-trumpcare/", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

A succinct rundown of what’s wrong with the Senate Republicans’ “health care” bill.

\n\n\n\n\t" - }, - { - "title" : "Nintendo: Switch Shortages Are ‘Definitely Not Intentional’", - "date_published" : "2017-06-23T17:46:13Z", - "date_modified" : "2017-06-23T17:46:15Z", - "id" : "https://daringfireball.net/linked/2017/06/23/nintendo-switch", - "url" : "https://daringfireball.net/linked/2017/06/23/nintendo-switch", - "external_url" : "https://arstechnica.com/gaming/2017/06/nintendo-switch-shortages-are-definitely-not-intentional/", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Kyle Orland, reporting for Ars Technica:

\n\n
\n

Since the days of the NES, people have accused Nintendo of\nintentionally underproducing hardware in order to drive an\nartificial feeding frenzy of demand in the marketplace. With the\nNintendo Switch remaining nearly impossible to find at retailers\nnationwide, those same accusations of “false scarcity” have been\nbubbling up in certain corners.

\n\n

Nintendo Senior Director of Corporate Communications Charlie\nScibetta wants to push back on those accusations. “It’s definitely\nnot intentional in terms of shorting the market,” he told Ars in a\nrecent interview. “We’re making it as fast as we can. We want to\nget as many units out as we can to support all the software that’s\ncoming out right now… our job really is to get it out as quick\nas we can, especially for this holiday because we want to have\nunits on shelves to support Super Mario Odyssey.”

\n
\n\n\n\n\t" - }, - { - "title" : "How to Build Smaller Apps", - "date_published" : "2017-06-22T23:03:38Z", - "date_modified" : "2017-06-22T23:03:40Z", - "id" : "https://daringfireball.net/linked/2017/06/22/sandofsky-app-bloat", - "url" : "https://daringfireball.net/linked/2017/06/22/sandofsky-app-bloat", - "external_url" : "https://blog.halide.cam/one-weird-trick-to-lose-size-c0a4013de331", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Ben Sandofsky:

\n\n
\n

Popular social networking apps are over 400 megs. With weekly\nreleases, over one year you’ll download twenty gigs of data.

\n\n

Since we launched Halide, the most unexpected compliment we’ve\nheard is about its size. At 11 megs, we’ll push less data in one\nyear than a social network pushes in a single update.

\n\n

“So you aren’t using Swift,” asked a friend. After all, Swift\nbundles its standard libraries into your app, bloating its size.\nHalide is almost entirely Swift. How did we do it? Let’s start\nwith the technical bits.

\n
\n\n

His conclusion is spot-on:

\n\n
\n

There really is one weird trick to lose size: focus on your customers.

\n
\n\n\n\n\t" - }, - { - "title" : "Bad App Citizens", - "date_published" : "2017-06-22T22:51:51Z", - "date_modified" : "2017-06-23T16:54:33Z", - "id" : "https://daringfireball.net/linked/2017/06/22/darke-bad-app-citizens", - "url" : "https://daringfireball.net/linked/2017/06/22/darke-bad-app-citizens", - "external_url" : "http://www.everyinteraction.com/blog/bad-app-citizens/", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Jon Darke:

\n\n
\n

This got me thinking — as a user who has a lot of apps\ninstalled, how much bandwidth does my phone use to keep my apps\nupdated? […]

\n\n

One Friday I turned off auto-update for apps and let the update\nqueue build up for a week. The results shocked me.

\n
\n\n

It’s getting to the point where most apps can’t be updated over cellular because they’re all over 100 MB. This is madness.

\n\n

Update: Many readers have written to argue that the listed sizes in the App Store aren’t what you actually download when updating an app, thanks to app thinning and other features. OK, but even with app thinning and delta updates these apps are still way too big as downloads and take up way too much storage on devices.

\n\n\n\n\t" - }, - { - "title" : "More Than 1,000 Uber Employees Ask for Travis Kalanick to Return", - "date_published" : "2017-06-22T22:24:24Z", - "date_modified" : "2017-06-22T22:24:26Z", - "id" : "https://daringfireball.net/linked/2017/06/22/uber-employees", - "url" : "https://daringfireball.net/linked/2017/06/22/uber-employees", - "external_url" : "https://www.axios.com/over-1-000-uber-employees-ask-for-travis-kalanick-to-return-2446313529.html", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Dan Primack, reporting for Axios:

\n\n
\n

More than one thousand current Uber employees have signed a letter\nto the company’s board of directors, asking for the return of\ndeposed CEO Travis Kalanick “in an operational role.” One of its\nventure capital investors also is chiming in, with a similar\nmessage.

\n
\n\n

Not surprising to me at all — Uber was made in Kalanick’s image.

\n\n\n\n\t" - }, - { - "title" : "Kara Swisher: ‘Susan Fowler Proved That One Person Can Make a Difference’", - "date_published" : "2017-06-22T20:15:16Z", - "date_modified" : "2017-06-22T20:15:18Z", - "id" : "https://daringfireball.net/linked/2017/06/22/fowler-swisher", - "url" : "https://daringfireball.net/linked/2017/06/22/fowler-swisher", - "external_url" : "https://www.recode.net/2017/6/21/15844852/uber-toxic-bro-company-culture-susan-fowler-blog-post", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Kara Swisher:

\n\n
\n

It was Lao Tzu who said that “the journey of a thousand miles\nbegins with a single step.”

\n\n

In the case of complete and utter change reeling through Uber\nright now — culminating in the resignation of its once\nuntouchable CEO Travis Kalanick — it turns out that it began with\none of the most epic blog posts to be written about what happens\nwhen a hot company becomes hostage to its increasingly\ndysfunctional and toxic behaviors.

\n\n

It was clear from the moment you read the 3,000-word post by\nformer engineer Susan Fowler about her time at the car-hailing\ncompany that nothing was going to be the same. Titled simply,\n“Reflecting on one very, very strange year at Uber,” the\nessay deftly and surgically laid out the map that the media and\nothers would use to prove to its out-to-lunch board and waffling\ninvestors that Uber CEO Travis Kalanick had to go.

\n
\n\n

The truth and courage are a powerful combination.

\n\n\n\n\t" - }, - { - "title" : "Gizmodo Investigation Exposes Websites Collecting Form Data Before You Hit ‘Submit’", - "date_published" : "2017-06-22T19:54:12Z", - "date_modified" : "2017-06-22T23:18:05Z", - "id" : "https://daringfireball.net/linked/2017/06/22/navistone-form-data", - "url" : "https://daringfireball.net/linked/2017/06/22/navistone-form-data", - "external_url" : "http://gizmodo.com/before-you-hit-submit-this-company-has-already-logge-1795906081", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Great investigative work by Kashmir Hill and Surya Mattu for Gizmodo:

\n\n
\n

During a recent investigation into how a drug-trial recruitment\ncompany called Acurian Health tracks down people who look online\nfor information about their medical conditions, we discovered\nNaviStone’s code on sites run by Acurian, Quicken Loans, a\ncontinuing education center, a clothing store for plus-sized\nwomen, and a host of other retailers. Using Javascript, those\nsites were transmitting information from people as soon as they\ntyped or auto-filled it into an online form. That way, the company\nwould have it even if those people immediately changed their minds\nand closed the page. […]

\n\n

We decided to test how the code works by pretending to shop on\nsites that use it and then browsing away without finalizing the\npurchase. Three sites — hardware site Rockler.com, gift site\nCollectionsEtc.com, and clothing site BostonProper.com — sent us\nemails about items we’d left in our shopping carts using the email\naddresses we’d typed onto the site but had not formally submitted.\nAlthough Gizmodo was able to see the email address information\nbeing sent to Navistone, the company said that it was not\nresponsible for those emails.

\n
\n\n

They weren’t responsible for sending the emails, but they were responsible for the email addresses being sent to those websites in the first place. Sending form data surreptitiously is morally wrong, and everyone knows it.

\n\n

This might sound hyperbolic, but I mean it: I think we’d be better off if JavaScript had never been added to web browsers.

\n\n\n\n\t" - }, - { - "title" : "Uber’s Biggest Problem: Its Business Model", - "date_published" : "2017-06-22T18:54:45Z", - "date_modified" : "2017-06-22T18:54:46Z", - "id" : "https://daringfireball.net/linked/2017/06/22/mims-uber", - "url" : "https://daringfireball.net/linked/2017/06/22/mims-uber", - "external_url" : "https://www.wsj.com/articles/with-kalanick-out-ubers-troubles-are-just-beginning-1498049054", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Christopher Mims, in his column for The Wall Street Journal:

\n\n
\n

But even when it steers through that thicket of crises, Uber will\nhave to come to grips with a fundamental vulnerability that is\nincreasingly apparent in the company’s business model. Uber may be\ngreat at technology, but unlike the businesses of Google,\nFacebook, Apple or Amazon, technology hasn’t proven to be a\nsignificant barrier to new entrants in ride-sharing. Across the\nglobe, Uber has dozens of competitors, and in many markets they\nhave grabbed the lion’s share of the ride-sharing market.

\n\n

Even if Uber fixes all of its current problems, it’s increasingly\nunlikely that it can live up to the inflated expectations that\ncome with the nearly $70 billion valuation that have made it the\nworld’s most valuable startup. There are barbarians at Uber’s\ngate, and it’s sorely in need of a moat.

\n
\n\n

This is why they’re pursuing self-driving technology so aggressively. There’s simply no way that Uber is worth $70 billion without some sort of exclusive technical advantage. That’s the interesting flip side to Kalanick’s ouster — I’m not sure who would want the job.

\n\n\n\n\t" - }, - { - "title" : "Chris Lattner on His Stint at Tesla", - "date_published" : "2017-06-22T18:26:01Z", - "date_modified" : "2017-06-22T19:13:09Z", - "id" : "https://daringfireball.net/linked/2017/06/22/lattner-resume", - "url" : "https://daringfireball.net/linked/2017/06/22/lattner-resume", - "external_url" : "http://www.nondot.org/sabre/Resume.html", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Chris Lattner has updated his resume with his accomplishments at Tesla. Unsurprisingly, it sounds like he got a lot done in just five months — including, ironically, addressing an engineering talent retention problem.

\n\n\n\n\t" - }, - { - "title" : "Inside Travis Kalanick’s Resignation", - "date_published" : "2017-06-22T18:07:14Z", - "date_modified" : "2017-06-22T18:07:15Z", - "id" : "https://daringfireball.net/linked/2017/06/22/kalanick-isaac", - "url" : "https://daringfireball.net/linked/2017/06/22/kalanick-isaac", - "external_url" : "https://www.nytimes.com/2017/06/21/technology/uber-travis-kalanick-final-hours.html", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Mike Isaac, reporting for The New York Times:

\n\n
\n

Travis Kalanick’s final hours as Uber’s chief executive played out\nin a private room in a downtown Chicago hotel on Tuesday.

\n\n

There, Mr. Kalanick, who was on a trip to interview executive\ncandidates for Uber, was paid a surprise visit. Two venture\ncapitalists — Matt Cohler and Peter Fenton of the Silicon Valley\nfirm Benchmark, which is one of Uber’s biggest shareholders —\npresented Mr. Kalanick with a list of demands, including his\nresignation before the end of the day. The letter was from five of\nUber’s major investors, including Benchmark and the mutual fund\ngiant Fidelity Investments. […]

\n\n

By the end of the day, after hours of haggling and arguing, that\ncourse was clear: Mr. Kalanick agreed to step down as Uber’s chief\nexecutive.

\n
\n\n

Truly great reporting from Isaac, including the fact that even during his brief “leave of absence”, he wasn’t really absent at all:

\n\n
\n

In reality, Mr. Kalanick had little intention of staying away from\nhis company. Almost immediately after announcing the leave of\nabsence, he worked the phones to push out Mr. Bonderman for making\nthe sexist comment onstage at an Uber employee meeting. With the\ntwo increasingly at odds, Mr. Kalanick sent out a flurry of texts,\nphone calls and emails to his allies to pressure Mr. Bonderman to\nstep down from Uber’s board. Hours later, Mr. Bonderman did.

\n
\n\n\n\n\t" - }, - { - "title" : "Narrative Maps for ‘Choose Your Own Adventure’ Books", - "date_published" : "2017-06-22T17:50:49Z", - "date_modified" : "2017-06-22T17:59:41Z", - "id" : "https://daringfireball.net/linked/2017/06/22/choose-your-own-adventure-maps", - "url" : "https://daringfireball.net/linked/2017/06/22/choose-your-own-adventure-maps", - "external_url" : "http://www.atlasobscura.com/articles/cyoa-choose-your-own-adventure-maps#coincidence", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Sarah Laskow, writing for Atlas Obscura:

\n\n
\n

The last installment of the original “Choose Your Own Adventure”\nseries came out in 1998, but since 2004, Chooseco, founded by\none of the series’ original authors, R.A. Montgomery, has been\nrepublishing classic volumes, as well as new riffs on the form of\ninteractive fiction that seemed ubiquitous in the 1980s and ’90s.\nThe new editions also carry an additional feature — maps of the\nhidden structure of each book.

\n
\n\n

Just looking at the maps included in the article, it’s interesting how wildly varying in complexity these stories were. See also: Christian Swinehart’s color-coded graphical representations of these books.

\n\n

(Via Kottke.)

\n\n\n\n\t" - }, - { - "title" : "John Markoff’s Interview With Scott Forstall and Members of the Original iPhone Team", - "date_published" : "2017-06-22T17:45:20Z", - "date_modified" : "2017-06-22T20:17:34Z", - "id" : "https://daringfireball.net/linked/2017/06/22/markoff-forstall", - "url" : "https://daringfireball.net/linked/2017/06/22/markoff-forstall", - "external_url" : "https://www.youtube.com/watch?v=5xDRdWFdsoQ", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

The Computer History Museum (now on YouTube):

\n\n
\n

Museum Historian John Markoff moderates a discussion with former\niPhone team members Hugo Fiennes, Nitin Ganatra and Scott Herz,\nfollowed by a conversation with Scott Forstall.

\n
\n\n

Fascinating stories.

\n\n

Forstall was great. It’s hard to believe he’s been out of Apple and out of the limelight for 5 years — watching him on stage with Markoff it feels like he never left.

\n\n\n\n\t" - }, - { - "title" : "‘Four Things in Those Two Sentences’", - "date_published" : "2017-06-21T16:55:23Z", - "date_modified" : "2017-06-21T16:55:26Z", - "id" : "https://daringfireball.net/linked/2017/06/21/swisher-kalanick", - "url" : "https://daringfireball.net/linked/2017/06/21/swisher-kalanick", - "external_url" : "https://www.recode.net/2017/6/21/15844848/uber-ceo-travis-kalanick-resigned-board-pressure", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Kara Swisher on Travis Kalanick:

\n\n
\n

Uber confirmed the resignation, and the company’s board issued a\nstatement that said, in part: “Travis has always put Uber first.\nThis is a bold decision and a sign of his devotion and love for\nUber.” (For those who don’t speak fluent tech director, there are\nfour things in those two sentences that are not true.)

\n
\n\n\n\n\t" - }, - { - "title" : "Uber Founder Travis Kalanick Resigns as C.E.O.", - "date_published" : "2017-06-21T16:53:04Z", - "date_modified" : "2017-06-21T16:53:06Z", - "id" : "https://daringfireball.net/linked/2017/06/21/kalanick-out", - "url" : "https://daringfireball.net/linked/2017/06/21/kalanick-out", - "external_url" : "https://www.nytimes.com/2017/06/21/technology/uber-ceo-travis-kalanick.html?_r=0", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Mike Isaac, reporting for The New York Times:

\n\n
\n

In the letter, titled “Moving Uber Forward” and obtained by The\nNew York Times, the investors wrote to Mr. Kalanick that he must\nimmediately leave and that the company needed a change in\nleadership. Mr. Kalanick, 40, consulted with at least one Uber\nboard member, and after long discussions with some of the\ninvestors, he agreed to step down. He will remain on Uber’s board\nof directors.

\n\n

“I love Uber more than anything in the world and at this difficult\nmoment in my personal life I have accepted the investors request\nto step aside so that Uber can go back to building rather than be\ndistracted with another fight,” Mr. Kalanick said in a statement.

\n
\n\n

From the outside, it seems like this was inevitable. It was only a question of when.

\n\n\n\n\t" - }, - { - "title" : "Chris Lattner Out at Tesla", - "date_published" : "2017-06-21T01:52:14Z", - "date_modified" : "2017-06-21T01:54:56Z", - "id" : "https://daringfireball.net/linked/2017/06/20/lattner-tesla", - "url" : "https://daringfireball.net/linked/2017/06/20/lattner-tesla", - "external_url" : "https://mobile.twitter.com/clattner_llvm/status/877341760812232704", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Chris Lattner:

\n\n
\n

Turns out that Tesla isn’t a good fit for me after all. I’m interested to hear about interesting roles for a seasoned engineering leader!

\n
\n\n

That was quick — he was only hired 5 months ago.

\n\n\n\n\t" - }, - { - "title" : "[Sponsor] MNML: A macOS Writing App for Medium.com", - "date_published" : "2017-06-20T23:12:26-04:00", - "date_modified" : "2017-06-24T20:23:18-04:00", - "id" : "https://daringfireball.net/feeds/sponsors/2017/06/mnml_a_macos_writing_app_for_m", - "url" : "https://daringfireball.net/feeds/sponsors/2017/06/mnml_a_macos_writing_app_for_m", - "external_url" : "http://mnml.af/", - "author" : { - "name" : "Daring Fireball Department of Commerce" - }, - "content_html" : "\n

Created for discerning Medium.com writers and publishers, it’s the first dedicated desktop publishing client on the Mac App Store. Featured Worldwide on release, it’s the last writing, blogging, and note-taking app you’ll need.

\n\n

Functional and fun yet mnml af. 🤔 😆 🔥

\n\n\n\n\t" - }, - { - "title" : "The Outline: ‘Inside Apple’s Global War on Leakers’", - "date_published" : "2017-06-20T18:22:34Z", - "date_modified" : "2017-06-20T18:22:37Z", - "id" : "https://daringfireball.net/linked/2017/06/20/apples-war-on-leaks", - "url" : "https://daringfireball.net/linked/2017/06/20/apples-war-on-leaks", - "external_url" : "https://theoutline.com/post/1766/leaked-recording-inside-apple-s-global-war-on-leakers", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

William Turton has quite a scoop for The Outline:

\n\n
\n

A recording of an internal briefing at Apple earlier this month\nobtained by The Outline sheds new light on how far the most\nvaluable company in the world will go to prevent leaks about new\nproducts. […]

\n\n

The briefing, which offers a revealing window into the company’s\nobsession with secrecy, was the first of many Apple is planning to\nhost for employees. In it, Rice and Freedman speak candidly about\nApple’s efforts to prevent leaks, discuss how previous leakers got\ncaught, and take questions from the approximately 100 attendees.

\n
\n\n

There’s some irony in a leaked recording of an internal briefing on stopping leaks.

\n\n

This is news to me:

\n\n
\n

However, Rice says, Apple has cracked down on leaks from its\nfactories so successfully that more breaches are now happening on\nApple’s campuses in California than its factories abroad. “Last\nyear was the first year that Apple [campuses] leaked more than the\nsupply chain,” Rice tells the room. “More stuff came out of Apple\n[campuses] last year than all of our supply chain combined.” […]

\n\n

In the years since Tim Cook pledged to double down on secrecy,\nRice’s team has gotten better at safeguarding enclosures. “In 2014\nwe had 387 enclosures stolen,” he says. “In 2015 we had 57\nenclosures stolen, 50 of which were stolen on the night of\nannounce, which was so painful.” In 2016, Rice says the company\nproduced 65 million housings, and only four were stolen. “So it’s\nabout a one in 16 million loss ratio, which is unheard of in the\nindustry.”

\n
\n\n

There’s a short (15 minute) podcast that accompanies the report, with Turton and The Outline’s Adrianne Jeffries. It’s worth a listen. (It doesn’t seem possible to link directly to a single episode of their podcast, so here’s a direct link for Overcast users.)

\n\n\n\n\t" - }, - { - "title" : "The Talk Show: ‘Egg Freckles’", - "date_published" : "2017-06-20T02:02:46Z", - "date_modified" : "2017-06-20T02:02:48Z", - "id" : "https://daringfireball.net/linked/2017/06/19/the-talk-show-194", - "url" : "https://daringfireball.net/linked/2017/06/19/the-talk-show-194", - "external_url" : "https://daringfireball.net/thetalkshow/2017/06/19/ep-194", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

New episode of my podcast, The Talk Show, with special guest Serenity Caldwell. We look back at WWDC 2017 — iOS 11, the new iPad Pro models, MacOS 10.13 “High Sierra”, updated Mac hardware and a tease at the upcoming iMac Pro, where Apple might go with VR and AR, San Jose as the venue for the event itself, and more.

\n\n

Sponsored by:

\n\n
    \n
  • Warby Parker: Boutique-quality, vintage-inspired glasses at a revolutionary price. Try up to five pairs at home for free.
  • \n
  • Squarespace: Make your next move. Make your next website. Use code gruber for 10% off your first order.
  • \n
  • Fracture: Your photos, printed beautifully directly on glass.
  • \n
\n\n\n\n\t" - }, - { - "title" : "★ Medium and the Scourge of Persistent Sharing Dickbars", - "date_published" : "2017-06-20T00:22:59Z", - "date_modified" : "2017-06-20T20:08:46Z", - "id" : "https://daringfireball.net/2017/06/medium_dickbars", - "url" : "https://daringfireball.net/2017/06/medium_dickbars", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Medium seems to continue to grow in popularity as a publishing platform, and as it does, I’m growing more and more frustrated by their on-screen “engagement” turds. Every Medium site displays an on-screen “sharing” bar that covers the actual content I want to read. This is particularly annoying on the phone, where screen real estate is most precious. Now on iOS they’ve added an “Open in App” button that literally makes the last 1-2 lines of content on screen unreadable. To me these things are as distracting as having someone wave their hand in front of my face while I try to read.

\n\n

Here’s an annotated screenshot (and threaded rant) I posted to Twitter while trying to read Steven Sinofksy’s WWDC 2017 trip report on my iPad Pro review unit last week.

\n\n

Safari already has a built-in Sharing button. It has all the options for sharing I need. And as I scroll the page, it disappears so that I can see as much text on screen as possible. Safari is designed to be reader-friendly, as it should be. But it’s trivial to get that Sharing button back when I want it – just tap the bottom of the screen and there it is. Easy.

\n\n

This is now a very common design pattern for mobile web layouts. Medium is far from alone. It’s getting hard to find a news site that doesn’t put a persistent sharing dickbar down there.

\n\n

More examples:

\n\n\n\n

TechCrunch’s waste of space deserves special mention, for having a persistent navbar at the top and a persistent ad, in addition to their sharing dickbar.

\n\n

I’m sure “engagement” does register higher with these sharing dickbars, but I suspect a big part of that is because of accidental taps. And even so, what is more important, readability or “engagement”? Medium wants to be about readability but that’s hard to square with this dickbar, and especially hard to square with the “Open in App” button floating above it.

\n\n

iOS also has a standard way to prompt users to install the app version of a website — Smart App Banners. And it’s user-dismissible.

\n\n

For any piece over a page long, I read Medium pieces with Safari’s Reader Mode. Medium is supposed to be a reader-optimized layout by default. It should be one of the sites where you’re never even tempted to switch to Reader Mode.

\n\n

I’m frustrated by this design pattern everywhere I see it. But I’m especially disappointed by Medium’s adoption of it. I don’t expect better from most websites. I do expect better from Medium.

\n\n

A website should not fight the browser. Let the browser provide the chrome, and simply provide the content. Web developers know this is right — these dickbars are being rammed down their throats by SEO experts. The SEO folks are the same dopes who came up with the genius strategy of requiring 5-10 megabytes of privacy-intrusive CPU-intensive JavaScript on every page load that slows down websites. Now they come to their teams and say, “Our pages are too slow — we gotta move to AMP so our pages load fast.”

\n\n

I don’t expect to break through to the SEO shitheads running the asylums at most of these publications, but Medium is supposed to be good. When people click a URL and see that it’s a Medium site, their reaction should be “Oh, good, a Medium site — this will be nice to read.” Right now it’s gotten to the point where when people realize an article is on Medium, they think, “Oh, crap, it’s on Medium.”

\n\n\n\n " - }, - { - "title" : "Microsoft Surface Laptop Teardown", - "date_published" : "2017-06-19T19:43:57Z", - "date_modified" : "2017-06-19T20:07:54Z", - "id" : "https://daringfireball.net/linked/2017/06/19/surface-laptop-ifixit", - "url" : "https://daringfireball.net/linked/2017/06/19/surface-laptop-ifixit", - "external_url" : "https://www.ifixit.com/Teardown/Microsoft+Surface+Laptop+Teardown/92915", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

iFixit gave the Surface Laptop a 0 out of 10 on their “Repairability Score”. The lowest anything from Apple has ever gotten is a 1, I believe.

\n\n
\n

Verdict: The Surface Laptop is not a laptop. It’s a glue-filled\nmonstrosity. There is nothing about it that is upgradable or\nlong-lasting, and it literally can’t be opened without destroying\nit. (Show us the procedure, Microsoft, we’d love to be wrong.)

\n
\n\n

iFixit’s point of view on this is logical, and they’re certainly not alone in wishing for the good old days of user-accessible and user-upgradeable components. But it’s silly to argue that the Surface Laptop is “not a laptop” only because it’s a sealed box. It’s like saying the iPhone is not a phone because it doesn’t have a replaceable battery.

\n\n

Update: Apple’s AirPods got a 0/10 from iFixit. That just goes to show how little correlation there is between iFixit’s concept of repairability and whether a product is good or not. I consider AirPods to be Apple’s best new product in years.

\n\n\n\n\t" - }, - { - "title" : "Standard Ebooks", - "date_published" : "2017-06-19T19:40:57Z", - "date_modified" : "2017-06-20T00:37:38Z", - "id" : "https://daringfireball.net/linked/2017/06/19/standard-ebooks", - "url" : "https://daringfireball.net/linked/2017/06/19/standard-ebooks", - "external_url" : "https://standardebooks.org/", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Standard Ebooks:

\n\n
\n

Standard Ebooks is a volunteer driven, not-for-profit project\nthat produces lovingly formatted, open source, and free public\ndomain ebooks.

\n\n

Ebook projects like Project Gutenberg transcribe ebooks and\nmake them available for the widest number of reading devices.\nStandard Ebooks takes ebooks from sources like Project\nGutenberg, formats and typesets them using a carefully designed\nand professional-grade style guide, lightly modernizes them,\nfully proofreads and corrects them, and then builds them to\ntake advantage of state-of-the-art ereader and browser\ntechnology. […]

\n\n

Other free ebooks don’t put much effort into professional-quality\ntypography: they use "straight" quotes instead of “curly” quotes,\nthey ignore details like em- and en-dashes, and they look more\nlike early-90’s web pages instead of actual books.

\n\n

The Standard Ebooks project applies a rigorous and modern\ntypography manual when developing each and every ebook to ensure\nthey meet a professional-grade and consistent typographical\nstandard. Our ebooks look good.

\n
\n\n

What a fantastic project. Project Gutenberg is an amazing library, but their books are a mess typographically. (Via Daniel Bogan.)

\n\n\n\n\t" - }, - { - "title" : "The Size of iPhone’s Top Apps Has Increased by 1,000 Percent in Four Years", - "date_published" : "2017-06-19T19:23:51Z", - "date_modified" : "2017-06-19T19:23:52Z", - "id" : "https://daringfireball.net/linked/2017/06/19/sensor-tower-ios-app-bloat", - "url" : "https://daringfireball.net/linked/2017/06/19/sensor-tower-ios-app-bloat", - "external_url" : "https://sensortower.com/blog/ios-app-size-growth", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Randy Nelson, writing for the Sensor Tower blog:

\n\n
\n

According to Sensor Tower’s analysis of App Intelligence,\nthe total space required by the top 10 most installed U.S. iPhone\napps has grown from 164 MB in May 2013 to about 1.8 GB last month,\nan 11× or approximately 1,000 percent increase in just four years.\nIn the following report, we delve deeper into which apps have\ngrown the most.

\n
\n\n

Apple really needs to do something about this. It’s not just that these apps are too big, but some of them issue software updates every week (or even more frequently). It’s a huge waste of bandwidth, time, and on-device storage space.

\n\n\n\n\t" - }, - { - "title" : "Microsoft AI Team Achieves Perfect Score on Atari 2600 Ms. Pac-Man", - "date_published" : "2017-06-19T19:14:49Z", - "date_modified" : "2017-06-19T19:17:41Z", - "id" : "https://daringfireball.net/linked/2017/06/19/ms-pac-man-ai", - "url" : "https://daringfireball.net/linked/2017/06/19/ms-pac-man-ai", - "external_url" : "https://www.theverge.com/2017/6/14/15801700/microsoft-ai-perfect-ms-pac-man-score", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Dani Deahl writing for The Verge:

\n\n
\n

At long last, the perfect score for arcade classic Ms. Pac-Man has\nbeen achieved, though not by a human. Maluuba — a deep learning\nteam acquired by Microsoft in January — has created an AI system\nthat’s learned how to reach the game’s maximum point value of\n999,900 on Atari 2600, using a unique combination of reinforcement\nlearning with a divide-and-conquer method.

\n
\n\n

Unlike the notoriously bad 2600 port of Pac-Man, the Ms. Pac-Man port was both fun and true to the spirit of the coin-op.

\n\n\n\n\t" - }, - { - "title" : "Why Reach Navigation Should Replace the Navbar in iOS Design", - "date_published" : "2017-06-19T18:52:46Z", - "date_modified" : "2017-06-19T18:57:25Z", - "id" : "https://daringfireball.net/linked/2017/06/19/ellis-reach-navigation", - "url" : "https://daringfireball.net/linked/2017/06/19/ellis-reach-navigation", - "external_url" : "https://medium.com/tall-west/lets-ditch-the-nav-bar-3692cb17cc67", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Brad Ellis:

\n\n
\n

As devices change, our visual language changes with them. It’s\ntime to move away from the navbar in favor of navigation within\nthumb-reach. For the purposes of this article, we’ll call that\nReach Navigation.

\n
\n\n

This design trend is clearly already underway, and Ellis does a terrific job explaining why it’s a good idea.

\n\n

I can think of a few factors that led to the original iPhone having a top-of-the-screen UI for navigation. First, at just 3.5 inches diagonally, the whole screen was reachable. But another factor might be as simple as the fact that “navigation” was always at the top on desktops — window titles and controls have always been at the top on Mac and Windows. The iPhone didn’t use windows, per se, but there was a certain familiarity with having the titles and controls like Back/Close/Done buttons at the top. Something like the iOS 10 bottom-heavy design of Apple Maps is wholly different from a desktop UI design — as it should be.

\n\n\n\n\t" - }, - { - "title" : "Ben Thompson on Amazon and Whole Foods", - "date_published" : "2017-06-19T18:25:15Z", - "date_modified" : "2017-06-19T18:25:16Z", - "id" : "https://daringfireball.net/linked/2017/06/19/thompson-amazon-whole-foods", - "url" : "https://daringfireball.net/linked/2017/06/19/thompson-amazon-whole-foods", - "external_url" : "https://stratechery.com/2017/amazons-new-customer/", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Great piece by Ben Thompson on Amazon’s intended acquisition of Whole Foods:

\n\n
\n

As Mackey surely understood, this meant that AmazonFresh was at a\ncost disadvantage to physical grocers as well: in order to be\ncompetitive AmazonFresh needed to stock a lot of perishable items;\nhowever, as long as AmazonFresh was not operating at meaningful\nscale a huge number of those perishable items would spoil. And,\ngiven the inherent local nature of groceries, scale needed to be\nachieved not on a national basis but a city one.

\n\n

Groceries are a fundamentally different problem that need a\nfundamentally different solution; what is so brilliant about this\ndeal, though, is that it solves the problem in a fundamentally\nAmazonian way.

\n
\n\n\n\n\t" - }, - { - "title" : "Ikea Details Plans for Furniture Placement App Powered by Apple’s ARKit", - "date_published" : "2017-06-19T17:59:44Z", - "date_modified" : "2017-06-19T17:59:46Z", - "id" : "https://daringfireball.net/linked/2017/06/19/ikea-arkit", - "url" : "https://daringfireball.net/linked/2017/06/19/ikea-arkit", - "external_url" : "https://www.macrumors.com/2017/06/19/ikea-plans-furniture-app-arkit/", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Mitchel Broussard:

\n\n
\n

At WWDC this year, Apple senior vice president of software\nengineering Craig Federighi performed a demo of the company’s new\naugmented reality platform, ARKit, while mentioning popular\nfurniture company IKEA as an upcoming partner in the technology.\nSimilarly, Apple CEO Tim Cook referenced an Ikea AR partnership in\na recent interview with Bloomberg Businessweek.

\n\n

Now, Ikea executive Michael Valdsgaard has spoken about the\ncompany’s partnership with Apple and ARKit, describing an all-new\naugmented reality app that will help customers make “reliable\nbuying decisions” for Ikea’s big ticket items.

\n
\n\n

Very cool idea — probably the sort of thing that’s going to be common soon. I’m curious how much of a leg up ARKit will give iOS on this front.

\n\n\n\n\t" - }, - { - "title" : "Squarespace Domains", - "date_published" : "2017-06-18T18:57:06Z", - "date_modified" : "2017-06-19T03:26:42Z", - "id" : "https://daringfireball.net/linked/2017/06/18/squarespace-domains", - "url" : "https://daringfireball.net/linked/2017/06/18/squarespace-domains", - "external_url" : "http://bit.ly/2kx5Ax5", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

My thanks to Squarespace for sponsoring last week’s DF RSS feed. Squarespace handles everything related to creating, hosting, and maintaining a website, including domain name registration.

\n\n

Buying a domain from Squarespace is quick, simple, and fun. Search for the domain you want, or type any word or phrase into the search field and Squarespace will suggest some great options. Every domain comes with a beautiful, ad-free parking page, WHOIS Privacy, and a 2048-bit SSL certificate to secure your website — all at no additional cost. Once you lock down your domain, create a beautiful website with one of Squarespace’s award-winning templates. Try Squarespace for free. When you’re ready to subscribe, get 10% off at squarespace.com with offer code “DARING17”.

\n\n\n\n\t" - }, - { - "title" : "Designing the Worst Volume Sliders Possible", - "date_published" : "2017-06-18T18:54:31Z", - "date_modified" : "2017-06-18T18:54:32Z", - "id" : "https://daringfireball.net/linked/2017/06/18/worst-volume-sliders", - "url" : "https://daringfireball.net/linked/2017/06/18/worst-volume-sliders", - "external_url" : "https://www.designernews.co/stories/84443-redditors-design-worst-volume-sliders-possible", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

This is a fun challenge.

\n\n\n\n\t" - }, - { - "title" : "John Markoff to Interview Scott Forstall Next Week", - "date_published" : "2017-06-15T19:34:33Z", - "date_modified" : "2017-06-15T20:03:29Z", - "id" : "https://daringfireball.net/linked/2017/06/15/markoff-forstall", - "url" : "https://daringfireball.net/linked/2017/06/15/markoff-forstall", - "external_url" : "http://www.computerhistory.org/events/upcoming/#putting-your-finger-it-creating", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Blockbuster event next week at the Computer History Museum in Mountain View:

\n\n
\n

How did iPhone come to be? On June 20, four members of the\noriginal development team will discuss the secret Apple project,\nwhich in the past decade has remade the computer industry, changed\nthe business landscape, and become a tool in the hands of more\nthan a billion people around the world.

\n\n

Part 1: Original iPhone Engineers Nitin Ganatra, Scott Herz, and\nHugo Fiennes in Conversation with John Markoff

\n\n

Part 2: Original iPhone Software Team Leader Scott Forstall in\nConversation with John Markoff

\n
\n\n

It kills me that I can’t make this. Hopefully there will be video.

\n\n

Here’s the thing: Forstall was obviously a divisive figure inside Apple. He saw himself as an indispensable man after Steve Jobs died, and it turns out he wasn’t.

\n\n

But there can be no dispute that Forstall led one of the most successful software projects ever undertaken. It’s a cliche to say that they achieved the impossible, but what Forstall’s team achieved was considered by many — including many of the members of the team — impossible. But they did it, and in the ensuing years they kept making iOS better and better. It’s not just that they managed to ship the original iPhone OS in June 2007, but the entire run up through Forstall’s ouster from the company was simply amazing.

\n\n

Across the company, it’s clear that Forstall’s style was not popular. But I know many people who worked on his iOS team, and most of them loved working for him, or at the very least appreciated working for him. The thing I’ve heard over and over is that Forstall was incredibly demanding, yes, but if you were on his team and did good work he had your back.

\n\n

Forstall pretty much hasn’t said a damn thing about Apple since he left the company five years ago. So if he opens up at all to Markoff, this could be fascinating. His team’s story about actually implementing the original iPhone remains largely untold.

\n\n\n\n\t" - }, - { - "title" : "Brian Merchant Has Tony Fadell on Tape", - "date_published" : "2017-06-15T19:16:54Z", - "date_modified" : "2017-06-15T19:16:55Z", - "id" : "https://daringfireball.net/linked/2017/06/15/merchant-fadell-on-tape", - "url" : "https://daringfireball.net/linked/2017/06/15/merchant-fadell-on-tape", - "external_url" : "https://www.theverge.com/2017/6/15/15804782/vergecast-brian-merchant-author-the-one-device-special-edition", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Nilay Patel, announcing a special episode of The Vergecast with The One Device author Brian Merchant:

\n\n
\n

And, of course, we talk about the quotes from Tony Fadell and\nBrett Bilbrey in the excerpt we just published, in which Fadell\ntells a story about Phil Schiller arguing the iPhone should have a\nhardware keyboard. Schiller has said the story isn’t true, and\nFadell has tried to walk it back as well.

\n\n

“So I wasn’t in the room at Apple 10, 15 years ago when this would\nhave happened,” says Merchant, who has the exchange on tape. “But\nthis is a quote verbatim as Tony Fadell who was in the room told\nit to me. He told me this quote in such detail and he gave such a\nvivid account, and I had no reason to believe it was untrue.”

\n\n

Merchant says the controversy has “blown him away.”

\n
\n\n

I figured Merchant had Fadell’s interview recorded. The quotes were too extensive not to have been recorded. It’s pretty clear what happened: Fadell told Merchant exactly what he’s quoted as saying, but now that he’s seen how it’s playing out, he wants to walk it back. It’s a little late for that.

\n\n\n\n\t" - }, - { - "title" : "★ iPad Pro Review Roundup", - "date_published" : "2017-06-14T03:59:00Z", - "date_modified" : "2017-06-14T21:22:44Z", - "id" : "https://daringfireball.net/2017/06/ipad_pro_review_roundup", - "url" : "https://daringfireball.net/2017/06/ipad_pro_review_roundup", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Federico Viticci, MacStories, “The 10.5-inch iPad Pro: Future-Proof”:

\n\n
\n

A good way to think about the iPad’s new display with ProMotion\nis not the difference between low-res and Retina screens, but\nthe jump from 30fps to 60fps. You see more of every animation.\nText is more legible when you scroll and doesn’t judder. It’s\nhard to explain and it has to be seen and experienced to be\nfully understood. Every scroll, page transition, and app launch\nanimation on the 10.5″ iPad Pro is absurdly smooth to the point\nof feeling unrealistic at first — hence the common reaction\nthat something doesn’t quite compute. But as you spend some time\nwith the new iPad and start using it on a daily basis, its\ndisplay becomes normal and you wish that other Apple displays\nwere the same.

\n\n

I’m not even a week into my tests with the 10.5″ iPad Pro, and\nI think scrolling on my first-gen 12.9″ iPad Pro looks choppy\nnow. I’d be surprised if 120Hz displays with ProMotion don’t\nexpand to the iPhone later this year and other Apple computers\nin the future. The combination of hardware and software really\nis that good.

\n
\n\n

Last year when True Tone was introduced with the 9.7-inch iPad Pro, Phil Schiller said something to the effect of “Once you get used to True Tone, you can’t go back.” I optimistically took that as a sign that the iPhone 7 would have True Tone. It did not, and the reason is probably that True Tone requires additional hardware sensors on the front face to pick up the ambient light temperature, and the iPhone has less room for additional sensors. But with ProMotion, I’m really hopeful that it’ll make its way into this year’s new iPhones. ProMotion doesn’t require additional sensors — only a super-fast GPU (which the iPhone will have) and intricate software support in iOS (which work Apple has already done for the iPad Pro).

\n\n

Anyway, it’s really hard to quote just one bit from Viticci’s review. If you only thoroughly read one review of the new iPad Pro, it should be his. Nobody outside Apple cares as much about iPad as he does.

\n\n
\n\n

Matthew Panzarino, TechCrunch, “Apple Pays Off Its Future-of-Computing Promise With iPad Pro”:

\n\n
\n

After playing with the new iPad Pro 10.5” for a few days, I am\nconvinced that it’s fairly impossible to do a detailed review of\nit in its current state.

\n\n

Not because there is some sort of flaw, but because it was clearly\ndesigned top to bottom as an empty vessel in which to pour iOS 11.

\n\n

Every feature, every hardware advancement, every piece of\nunderstated technical acrobatics is in the service of making\nApple’s next-generation software shine.

\n
\n\n
\n\n

Dieter Bohn, The Verge, “iPad Pro 10.5 Review: Overkill”:

\n\n
\n

I was all set to complain that increasing the size from 9.7 to\n10.5 was not a big enough jump to justify requiring people to buy\nnew keyboards and accessories. Then I started typing on the\non-screen keyboard and on the new hardware Smart Keyboard. Even\nthough I’m dubious about Apple’s claim that the software keyboard\nis “full size”, I find the slight size increase makes touch typing\nmuch easier. It’s still a little cramped, but it’s much easier to\nbounce between this and a real keyboard now.

\n
\n\n

It really does make a difference in typing, and no practical difference at all in terms of holdability.

\n\n

Bohn again:

\n\n
\n

To me, if you’re going to spend $650 on a computer, it should\nalmost surely be your main computer. And if you’re going to make\nthe iPad Pro your main computer, you should probably get more than\n64GB of storage and you should also probably get a keyboard to go\nwith it (to say nothing of the Apple Pencil). It hits the $1,000\nmark very quickly.

\n
\n\n

I don’t agree with the notion that a $650 computer should be your “main computer” at all. Apple stuff isn’t for the budget-conscious — news at 11.

\n\n
\n\n

Brian X. Chen, The New York Times, “New iPad Pro Inches Toward Replacing PC, but Falls Short”:

\n\n
\n

Five years later, Mr. Jobs’s successor, Timothy D. Cook, took the\niPad a step further. Unveiling the iPad Pro, a souped-up tablet\nthat worked with Apple’s keyboard and stylus, he remarked that\npeople would try the product and “conclude they no longer need to\nuse anything else, other than their phones.”

\n\n

That prediction has not appeared to come true. Many professionals\nsay they use an iPad in addition to a personal computer, and sales\nof iPads have shrunk quarter after quarter for more than a year,\nan indication that hordes of people were not trading in their PCs\nfor tablets just yet.

\n\n

That situation is unlikely to change with Apple’s newest iPad Pro,\nwhich will be released this week. […] But after about a week of\ntesting the 10.5-inch iPad Pro, I concluded that Apple’s\nprofessional tablet still suffers from some of the same problems\nwhen compared with a laptop.

\n
\n\n

That’s a slanted truncation of Cook’s quote. Cook’s full quote: “Yes, the iPad Pro is a replacement for a notebook or a desktop for many, many people. They will start using it and conclude they no longer need to use anything else, other than their phones.” Chen’s truncation makes it sound like Cook claimed the iPad Pro was a Mac or Windows replacement for everyone. He didn’t. And the fact that the new iPad Pro debuted alongside new MacBooks, MacBook Pros, and even more-megahertz-in-the-box MacBook Airs shows that Apple doesn’t think so either. Update: And I completely forgot to mention the solid updates to the iMacs and the announcement of the iMac Pro.

\n\n

I prefer a laptop to an iPad Pro” is very different from “A laptop is better than an iPad Pro”. Me, personally, I much prefer working on a MacBook Pro to an iPad Pro. But I can see why others feel the opposite. That’s the whole point of Apple’s strategy of keeping them separate, rather than unifying them Microsoft Surface-style.

\n\n

iPad’s slowly diminishing sales are a real thing. But I don’t think that can be used as a gauge for whether more and more people are using an iPad as their main computer. And iPad sales are still more than double those of the Mac. There’s no reason to doubt that “many, many people” are concluding they no longer need a Mac or PC.

\n\n
\n\n

Andrew Cunningham, Ars Technica, “The 10.5-Inch iPad Pro Is Much More “Pro” Than What It Replaces”:

\n\n
\n

Of all the computers Apple sells, none of them has screens that do\nquite as much stuff as the iPad Pros are doing.

\n\n

That list starts with DCI-P3 color gamut support (new in the\n12.9-inch Pro, returning to the smaller one) and an\nanti-reflective coating, features also present in recent iMacs and\nMacBook Pros. But the True Tone feature, which detects the color\ntemperature of the ambient light, adjusts the display’s color\ntemperature to match. Most significantly, the iPad’s refresh rate\nhas been bumped up to 120Hz, twice the normal 60Hz. The screens in\nthe iPad Pros are the best screens Apple ships, which is\nappropriate for a thing that’s just a giant screen by design.

\n\n

The 10.5-inch Pro has a 2224×1668 screen, up just a little bit\nfrom the 2048×1536 in 9.7-inch iPads. The density is identical, so\nphotos and text are exactly the same size they were before; you\ncan just fit a bit more of them on-screen at once.

\n
\n\n

That’s important to note. There was some clever speculation by Dan Provost a few months ago that the 10.5-inch iPad would have the same pixel dimensions as the 12.9-inch iPad Pro, with a higher pixels-per-inch density. That’s what Apple did with the iPad Mini. The problem with that speculation is that while the math worked out, the size of things on screen would not. Everything would be shrunk by 20 percent. Not everyone’s eyes can handle that. That’s fine for the Mini — which is often used by sharp-eyed children — but not fine for the standard size iPad.

\n\n

I had been thinking that maybe what Apple would do is what Provost suggested, but offer a choice between standard and zoomed mode like the Plus-sized iPhones do. Nope. I think what they’ve done is better though, because I think a scaled “zoomed” interface would look blurry at just 324 ppi. The iPhone Plus displays have a resolution of 401 ppi.

\n\n
\n\n

Harry McCracken, Fast Company, “A Better Window Into The World Of Apps”:

\n\n
\n

You can suss out a lot about Apple’s priorities from the aspects\nof a product it leaves alone and the ones it never stops\nobsessing over.

\n\n

Consider the iPad. Every generation of Apple’s tablet since the\nfirst one in 2010 has had the same stated battery life–“up to 10\nhours”–which suggests that the company thinks that shooting for\nanything in excess of that would be wasted effort.

\n\n

That 2010 iPad weighed a pound and a half, and felt a bit hefty in\nthe hand. With 2013’s iPad Air, Apple whittled that down to about\na pound. And there the mid-sized iPads have stayed,\nweight-reduction mission accomplished.

\n\n

However, when it comes to the iPad’s display, Apple has never been\nsatisfied to rest on its technological laurels.

\n
\n\n\n\n " - }, - { - "title" : "★ The Knives Come Out for Phil Schiller in Brian Merchant’s ‘The One Device: The Secret History of the iPhone’", - "date_published" : "2017-06-13T20:46:25Z", - "date_modified" : "2017-06-13T23:15:31Z", - "id" : "https://daringfireball.net/2017/06/the_knives_come_out_for_schiller", - "url" : "https://daringfireball.net/2017/06/the_knives_come_out_for_schiller", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

The Verge has an exclusive (and lengthy) excerpt from Brian Merchant’s The One Device: The Secret History of the iPhone, which comes out next week. Merchant seemingly has many first-hand sources on the record, including Tony Fadell and perhaps Scott Forstall. (I say “perhaps” because it’s not clear from the excerpt whether Forstall spoke to Merchant, or if Merchant got the Forstall quotes from somewhere else. It seems like there should be a lot more from Forstall in this story if he actually talked to Merchant.)

\n\n

But Fadell spoke to Merchant extensively, including this shot at Phil Schiller:

\n\n
\n

The iPod phone was losing support. The executives debated which\nproject to pursue, but Phil Schiller, Apple’s head of marketing,\nhad an answer: Neither. He wanted a keyboard with hard buttons.\nThe BlackBerry was arguably the first hit smartphone. It had an\nemail client and a tiny hard keyboard. After everyone else,\nincluding Fadell, started to agree that multitouch was the way\nforward, Schiller became the lone holdout.

\n\n

He “just sat there with his sword out every time, going, ‘No,\nwe’ve got to have a hard keyboard. No. Hard keyboard.’ And he\nwouldn’t listen to reason as all of us were like, ‘No, this works\nnow, Phil.’ And he’d say, ‘You gotta have a hard keyboard!’”\nFadell says.

\n
\n\n

I don’t know if it’s true or not that Schiller was singlehandedly pushing for a Blackberry-style keyboard. But even if true, it only looks foolish in hindsight, especially if this argument took place before the iPhone’s software team had come up with a proof-of-concept software keyboard. Today it’s clear that the iPhone needed a good keyboard, and that a touchscreen keyboard can be a good keyboard. Neither of those things was obvious in 2005. And in the context of this story, it’s clear that at the time of this purported argument, Steve Jobs and Apple weren’t yet sure if the iPhone should be a pocket-sized personal computer or a consumer electronics product that would have no more need for a keyboard (hardware or software) than an iPod did. My guess is that Schiller was insisting that the iPhone needed to be a personal computer, not a mere gadget, and it wasn’t unreasonable to believe a software keyboard wouldn’t be good enough. For chrissakes there were critics who insisted that the iPhone’s software keyboard wasn’t good enough for years after the iPhone actually shipped.

\n\n

I do know that Schiller’s hard-charging, brusque style and his obvious political acumen have made him a lot of enemies over the years. It sounds like Fadell is one of them.

\n\n

So I’ll just say this: this story about Phil Schiller pushing for a hardware keyboard comes from one source (so far — if anyone out there can back that up, my window is always open for little birdies), and that one source is the guy who admittedly spent over a year working on iPhone prototypes with a click wheel interface.

\n\n

Then there’s this:

\n\n
\n

Schiller didn’t have the same technological acumen as many of the\nother execs. “Phil is not a technology guy,” Brett Bilbrey, the\nformer head of Apple’s Advanced Technology Group, says. “There\nwere days when you had to explain things to him like a\ngrade-school kid.” Jobs liked him, Bilbrey thinks, because he\n“looked at technology like middle America does, like Grandma and\nGrandpa did.”

\n
\n\n

Hats off to Bilbrey for putting his name on this quote, but having spoken to Schiller both on- and off-the-record many times, the idea that he “looks at technology … like Grandma and Grandpa did” and needs things explained to him “like a grade-school kid” is bullshit. Especially off-the-record, Schiller can drill down on technical details to a surprising degree. I don’t know what Schiller did to piss off Bilbrey, but Bilbrey either has a huge chip on his shoulder or was severely misquoted by Merchant.1

\n\n

Anyway, I sure wish this book excerpt had come out before my live episode of The Talk Show last week — now I do have one more question I wish I’d gotten to ask Schiller.

\n\n
\n
\n
    \n
  1. \n

    Here’s a story from Yoni Heisler for Network World on Brett Bilbrey’s retirement from Apple in 2014. Bilbrey headed Apple’s Technology Advancement Group. Merchant describes Bilbrey as having led “Apple’s Advanced Technology Group”. It’s a small detail, and the names are clearly similar, but the Advanced Technology Group was Larry Tesler’s R&D division at Apple, from 1986-1997. It was among the numerous divisions and products that Steve Jobs shitcanned after he rejoined the company. ↩︎

    \n
  2. \n
\n
\n\n\n\n " - }, - { - "title" : "★ The 2017 iPad Pros", - "date_published" : "2017-06-12T22:33:42Z", - "date_modified" : "2017-06-13T23:15:34Z", - "id" : "https://daringfireball.net/2017/06/the_2017_ipad_pros", - "url" : "https://daringfireball.net/2017/06/the_2017_ipad_pros", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

I’ve spent the last week using a new 10.5-inch iPad Pro, and this is, in many ways, the easiest product review I’ve ever written. There are several significant improvements to the hardware, and no tradeoffs or downsides. There is no “but”.

\n\n
    \n
  • Display: The new iPad Pros have the best displays of any computer I’ve ever seen. True Tone plus ProMotion is simply terrific. (The first generation 12.9-inch iPad Pro didn’t have True Tone; with these new models, the only noticeable difference between the 12.9- and 10.5-inch models is the size.) You really do have to see the 120 Hz refresh rate in person — and play with it while scrolling content on screen — to get it. You can actually read text as it’s moving during a scroll. It’s not as significant as the jump from non-retina to retina, but it’s in that ballpark.

  • \n
  • Pencil: The latency of the Apple Pencil on a first-generation iPad Pro is the best I had ever seen for any stylus on any device at any price. The latency of the Apple Pencil on the new iPad Pro is so much better — so much closer to ink-on-paper imperceptibility — that you have to try it to believe it. It’s the one thing that really makes the first-gen iPad Pro feel “slow”.

  • \n
  • Size: The increase in size is perfect. The footprint for a “regular” iPad has, until now, remained unchanged since the original iPad in 2010. That 9.7-inch display size was nearly perfect. This 10.5-inch display size is better though. Apple said during the keynote that typing on the on-screen keyboard is surprisingly better given just a bit more room, and I agree. And typing on the Smart Keyboard cover is way better than on last year’s 9.7-inch iPad Pro. In hand it doesn’t feel bigger at all. It feels like there were no trade-offs whatsoever in increasing the display size and overall device footprint. Part of that is because the weight has remained completely unchanged. I have had zero problems — not one — with the decreased bezel area. Apple’s inadvertent touch detection game is on point.

  • \n
  • Battery: Battery life is great, as expected.

  • \n
  • Performance: Apple’s in-house chip team continues to amaze. No one buys an iPad because of CPU benchmarks, but the new iPad Pro’s CPU performance is mind-boggling. Forget about comparisons to the one-port MacBook — the iPad Pro blows that machine out of the water performance-wise. The astounding thing is that the new iPad Pro holds its own against the MacBook Pro in single-core performance — around 3,900 on the Geekbench 4 benchmark for the iPad Pro vs. around 4,200–4,400 for the various configurations of 13- and 15-inch MacBook Pros.1 Multi-core performance has effectively doubled from the first generation of iPad Pro. That sort of year-over-year increase just doesn’t happen anymore, but here we are. The new iPad Pro gets a multi-core Geekbench 4 score of around 9200; the brand-new Core M3-based MacBook gets a multi-core score of around 6800. Again, this isn’t why people buy iPads — the iPad took off like a rocket in 2010 back when it was way slower (way way way slower) than even the slowest MacBook — but I think it is vastly underappreciated just how significantly Apple’s chip team is pulling ahead of the industry, especially Intel.2

  • \n
\n\n
\n\n

All that said, the real story of these new iPad Pro models can’t be told today, because that story is iOS 11. I think iOS 11’s iPad-focused features are the entire reason why Apple waited until WWDC to unveil them. They could have held an event for them back in April, when they released the new starting-at-just-$329 9.7-inch iPad, but if they did, the only new software they could have demoed was Clips. I love Clips, but it’s just a fun little tool and doesn’t show off anything particular to iPad compared to iPhone.

\n\n

Again, the new iPad Pro hardware is almost too good to be true, but the iPad story Apple unveiled last week is iOS 11.

\n\n

It’s not fair to review a product running a developer beta of the OS — let alone the first (and generally buggiest) beta. So let’s stop the “review” right here: the new iPad Pros running iOS 10.3.2 are the best iPads ever made. You shouldn’t hesitate to buy one today, and if you do get one now, you should wait until iOS 11 ships in the fall to upgrade, or at the very least wait for a non-developer public beta of iOS 11 this summer before upgrading.

\n\n

But if you are reckless enough to install the iOS 11 beta on the new iPad Pro? Holy smokes is this better. I used the iPad Pro for a full week with iOS 10.3.2 because that’s the product that’s shipping, but after upgrading to iOS 11 beta 1 this morning and using it to write this entire review,3 I’m just blown away by how much more useful this machine is, and how much easier it is to work with 5 or 6 apps at a time.

\n\n

I would never recommend running a beta of any OS on any device that’s used for production purposes, so don’t take this as such, but for me personally, I can’t see going back to iOS 10.3.2 on any iPad that can handle it. It feels like a hand has been untied from behind my back, and this amazing hardware has finally been allowed to run free.

\n\n
\n
\n
    \n\n
  1. \n

    You can browse Geekbench’s database of results for Mac and iOS↩︎

    \n
  2. \n\n
  3. \n

    Apple’s A10X chip is so high-performing that I think it’s put Apple in a slightly uncomfortable position marketing-wise. They can’t brag about it fully without making Intel (and by implication, their own MacBooks) look bad, and Intel remains an important partner for Apple. I don’t think it’s a coincidence that the most impressive iPad demo during the keynote (and the one that contained the most bragging about its performance compared to PCs) was done by a third-party developer — Ash Hewson of Serif, demonstrating Affinity Photo — not Greg Joswiak or anyone else from Apple. ↩︎︎

    \n
  4. \n\n
  5. \n

    Full disclosure: I went back to my Mac to write these 3 footnotes. That’s due more to the byzantine way I mark up footnotes than any limitation inherent to iOS vs. MacOS. But it feels worth noting. ↩︎︎

    \n
  6. \n\n
\n
\n\n\n\n " - }, - { - "title" : "★ In-App Purchase Scams in the App Store", - "date_published" : "2017-06-10T20:20:10Z", - "date_modified" : "2017-06-10T20:20:12Z", - "id" : "https://daringfireball.net/2017/06/in-app_purchase_scams_in_the_app_store", - "url" : "https://daringfireball.net/2017/06/in-app_purchase_scams_in_the_app_store", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

Great investigative piece by Johnny Lin looking into a top-10 highest grossing app named “Mobile protection :Clean & Security VPN” (punctuation and grammatical errors sic), from a developer named Ngan Vo Thi Thuy:

\n\n
\n

“Full Virus, Malware scanner”: What? I’m pretty sure it’s\nimpossible for any app to scan my iPhone for viruses or malware,\nsince third party apps are sandboxed to their own data, but let’s\nkeep reading…

\n\n

“You will pay $99.99 for a 7-day subscription”

\n\n

Uhh… come again?

\n
\n\n

There should be no “virus and malware” scanners in the App Store. None. iOS does not need anti-virus software. The App Store sandboxing rules mean that anti-virus software couldn’t really do anything useful anyway. And by allowing them to be listed on the store, it creates the false impression that Apple thinks you might need anti-virus software.

\n\n

But do-nothing anti-virus utilities that are scamming people into $100/week subscriptions? That’s downright criminal.

\n\n

Lin shows that “Mobile protection :Clean & Security VPN” is not alone. The productivity top-grossing list is riddled with similar scam apps.

\n\n

Given how many legitimate developers are still having problems getting their apps approved due to seemingly capricious App Store reviewer decisions, it’s doubly outrageous that these apps have made their way onto the store in the first place. These are the exact sort of apps that the App Store review process should be primarily looking to block.

\n\n

And there is no excuse for Apple not having flagged them after the fact, once they started generating significant revenue. It’s downright mind boggling that this horrendous “Mobile protection :Clean & Security VPN” app made it all the way into the top 10 without getting flagged.

\n\n

Based on Lin’s research, the pattern is simple:

\n\n
    \n
  1. Create a scammy utility app and get it into the store. Make it a free download with an expensive in-app subscription.
  2. \n
  3. Bid on common keywords like “virus”, “VPN”, and “wi-fi”.
  4. \n
  5. Get tens of thousands of downloads thanks to the top-of-results placement from the ad.
  6. \n
  7. Spring the in-app subscription prompt on your users and make money with a response rate of even just a fraction of 1 percent.
  8. \n
\n\n

Apple needs to remove these apps from the App Store, and prevent such apps from getting into the store in the first place. They should reconsider the effects of allowing developers to buy their way to the top spot in search results. And they should police the top-grossing lists for apps that are pulling scams — the most important scams to catch are the successful ones.

\n\n

Lastly, every single dollar these apps have generated should be refunded to the victims of these scams.

\n\n\n\n " - }, - { - "title" : "★ Update on The Talk Show Live From WWDC 2017", - "date_published" : "2017-06-02T05:41:06Z", - "date_modified" : "2017-06-02T06:13:18Z", - "id" : "https://daringfireball.net/2017/06/update_on_the_talk_show_live_from_wwdc_2017", - "url" : "https://daringfireball.net/2017/06/update_on_the_talk_show_live_from_wwdc_2017", - "author" : { - "name" : "John Gruber" - }, - "content_html" : "\n

On Wednesday I put the first 500 tickets on sale for next week’s live show from WWDC. They sold out in 7 minutes.

\n\n

The California Theatre in San Jose has both an orchestra level and a balcony. That first bunch of tickets separated the two. After talking with the staff at the theater today, they recommended making all tickets general admission and allowing their ushers to fill the orchestra level first, and then direct remaining ticket holders to the balcony. So, all tickets, including those sold Wednesday, are now simply general admission. Everyone paid the same price, so I think this is fair, but I do apologize for any confusion. The theater is beautiful, and there are no bad seats.

\n\n

The next batch of tickets will go on sale today, Friday, at 1p ET/10a PT. Given what happened Wednesday, I expect them to sell out in a few minutes. I hate writing that because it sounds braggy, but I’m putting it out there just as fair warning. You’re going to have to act quick and maybe get lucky.

\n\n

If you want a ticket and wind up not getting one, there will be a live audio stream for everyone to listen to. This year we are not going to attempt to stream live video. Instead we’re going to work hard to get edited video of the event up on the web as soon as possible after the show is over. If you just can’t wait, listen to the live audio. If you want to see the show, wait for the video — it should be up some time on Wednesday at the latest.

\n\n

If you do get a ticket or already have one:

\n\n
    \n
  • All seats are general admission, with no distinction between orchestra level and the balcony.
  • \n
  • Put your ticket in Apple Wallet and bring ID to show at the door. (If you don’t have an iPhone (?) bring a copy of the ticket PDF.)
  • \n
  • Doors open at 6p, and there will be an open bar. Find a seat, grab a beverage, and mingle with your fellow fans of the show.
  • \n
  • The show itself should start at 7p.
  • \n
\n\n\n\n " - } - ] -} diff --git a/RSParser/Tests/RSParserTests/Resources/DaringFireball.rss b/RSParser/Tests/RSParserTests/Resources/DaringFireball.rss deleted file mode 100755 index ba3d1400a..000000000 --- a/RSParser/Tests/RSParserTests/Resources/DaringFireball.rss +++ /dev/null @@ -1,2278 +0,0 @@ - - -Daring Fireball -By John Gruber - - -http://daringfireball.net/feeds/main - -2016-02-28T21:06:52ZCopyright © 2016, John Gruber - Apple Product Event: Monday March 21 - - - - tag:daringfireball.net,2016:/linked//6.32173 - 2016-02-27T21:59:47Z - 2016-02-27T22:39:17Z - - John Gruber - http://daringfireball.net/ - - Kara Swisher, writing at Recode, broke the news:

- -
-

Attention Apple nerds, investors, media and everyone else who -needs to know when Tim Cook’s next product event is going to be -held: It’s going to be the week of March 21.

- -

Or to put it another way, it’s not going to be on March 15, the -time frame that other outlets previously reported, according to -several sources. It is not clear if the event was moved or if this -was the same timing as Apple had always planned.

-
- -

Swisher doesn’t have the exact date, although the <title> tag on her story reads “Apple Product Event Will Be Held March 22”. John Paczkowski (who usually gets these leaks first), confirms the week change, and says the event will be on Monday 21 March:

- -
-

Sources in position to know say the company has settled on March -21st as the date it will show off a handful of new products. These -people declined to say why Apple postponed the date by a week, but -it’s worth noting that it is one day prior to the company’s March -22 showdown with the government over a motion to compel it to help -hack the iPhone used by one of the San Bernardino terrorists.

-
- -

For what it’s worth, last year’s March event was on a Monday as well.

- -

Update: Jim Dalrymple:

- -
-

This sounds right to me.

-
- - - - ]]>
-
- Manuscripts and Findings - - - - tag:daringfireball.net,2016:/linked//6.32172 - 2016-02-27T00:11:11Z - 2016-02-27T00:13:11Z - - John Gruber - http://daringfireball.net/ - - My thanks to Nucleobytes for sponsoring this week’s DF RSS feed. Nucleobytes is a fascinating company. They specialize in creating Mac and iOS software for scientists and researchers, and they do it with great style — their apps have won multiple Apple Design Awards.

- -

Their latest creations are two apps for researchers, useful for anyone who researches anything from lab results, cooking recipes, or research for blog posts: Manuscripts and Findings.

- -
    -
  • Manuscripts is a writing tool that helps you concentrate on your story. Outline, plan and edit your project, insert figures, tables and math, then format citations using a killer workflow. Manuscripts supports both importing and exporting Markdown, Word, LaTeX, and HTML.

  • -
  • Findings is a lab notebook app that helps you keep a journal of your research, connected to notes, photos, and files. Plan your week, track progress, and share your findings with your colleagues or the world.

  • -
- -

Try the free basic versions, and use coupon DARINGFIREBALL for a special discount on the unlimited versions, this week only. (They have an even better offer for students.)

- - - - ]]>
-
- Donald Trump Vows to ‘Open Up’ Libel Laws - - - - tag:daringfireball.net,2016:/linked//6.32171 - 2016-02-26T21:47:27Z - 2016-02-26T21:47:28Z - - John Gruber - http://daringfireball.net/ - - Hadas Gold, writing for Politico:

- -
-

During a rally in Fort Worth, Texas, Trump began his usual tirade -against newspapers such as The New York Times and The Washington -Post, saying they’re “losing money” and are “dishonest.” The -Republican presidential candidate then took a different turn, -suggesting that when he’s president they’ll “have problems.”

- -

“One of the things I’m going to do if I win, and I hope we do and -we’re certainly leading. I’m going to open up our libel laws so -when they write purposely negative and horrible and false -articles, we can sue them and win lots of money. We’re going to -open up those libel laws. So when The New York Times writes a hit -piece which is a total disgrace or when The Washington Post, which -is there for other reasons, writes a hit piece, we can sue them -and win money instead of having no chance of winning because -they’re totally protected,” Trump said.

-
- -

Not worrisome at all. No sir.

- - - - ]]>
-
- Most Android Phones Are Not Encrypted - - - - tag:daringfireball.net,2016:/linked//6.32170 - 2016-02-26T17:43:11Z - 2016-02-28T21:06:52Z - - John Gruber - http://daringfireball.net/ - - Jose Pagliery, writing for CNN Money:

- -
-

Although 97% of Android phones have encryption as an option, less -than 35% of them actually got prompted to turn it on when they -first activated the phone. Even then, not everybody chooses that -extra layer of security.

- -

A Google spokesman said that encryption is now required for all -“high-performing devices” — like the Galaxy S7 — running the -latest version of Android, Marshmallow. But only 1.2% of Android -phones even have that version, according to Google.

- -

By comparison, most Apple products are uniformly secure: 94% of -iPhones run iOS 8 or 9, which encrypt all data. Apple (AAPL, -Tech30) makes its devices, designs the software, and retains full -control of the phone’s operating system.

- -

“If a person walks into a Best Buy and walks out with an iPhone, -it’s encrypted by default. If they walk out with an Android phone, -it’s largely vulnerable to surveillance,” said Christopher -Soghoian, the principal technologist at the American Civil -Liberties Union.

-
- -

Google is moving in the right direction, but here’s an area where the slow uptake of new versions of Android has a serious effect.

- - - - ]]>
-
- 9to5Mac: ‘Apple Likely to Drop the “5”, Call New 4-Inch Model the “iPhone SE”’ - - - - tag:daringfireball.net,2016:/linked//6.32169 - 2016-02-26T17:24:11Z - 2016-02-26T18:32:34Z - - John Gruber - http://daringfireball.net/ - - Mark Gurman:

- -
-

In January, we reported that Apple is preparing a new 4-inch -iPhone that is essentially 2013’s iPhone 5s with upgraded -internals. At the time, we heard that Apple would call the device -the “iPhone 5se” based on it being both an enhanced and “special -edition” version of the iPhone 5s. Now, we are hearing that Apple -appears to be going all in on the special edition factor: sources -say that Apple has decided to drop the “5” from the device’s name -and simply call it the “iPhone SE.” This will mark the first -iPhone upgrade without a number in its name and would logically -remove it from a yearly update cycle.

-
- -

A few points:

- -
    -
  • Apple was never going to call this phone the “5 SE”. I don’t know where Gurman got that, but that was never going to happen. Why would Apple give a new phone a name that makes it sound old?

  • -
  • Isn’t it more accurate to think of this as an iPhone 6S in a 4-inch body than as an iPhone 5S with “upgraded internals”? Other than the display, aren’t the “internals” the defining characteristics of any iPhone?

  • -
  • Dropping the number entirely fits with my theory that this phone is intended to remain on the market for 18-24 months.

  • -
- - - - ]]>
-
- Gogo Wi-Fi and Email Security - - - - tag:daringfireball.net,2016:/linked//6.32168 - 2016-02-26T17:12:34Z - 2016-02-26T19:00:17Z - - John Gruber - http://daringfireball.net/ - - Reporter Steven Petrow published a scary first-hand tale in USA Today, claiming that his email was hacked by another passenger on a Gogo-enabled flight. The implication was that you shouldn’t use email on Gogo unless you’re using a VPN.

- -

But Petrow’s email didn’t get intercepted because of some flaw with Gogo. It got intercepted because he wasn’t connecting to the POP or SMTP servers via SSL. In fact, his email provider, Earthlink, doesn’t even support SSL for email.

- -

Robert Graham at Errata Security explains:

- -
-

Early Internet stuff wasn’t encrypted, because encryption was -hard, and it was hard for bad guys to tap into wires to eavesdrop. -Now, with open WiFi hotspots at Starbucks or on the airplane, it’s -easy for hackers to eavesdrop on your network traffic. -Simultaneously, encryption has become a lot easier. All new -companies, those still fighting to acquire new customers, have -thus upgraded their infrastructure to support encryption. Stagnant -old companies, who are just milking their customers for profits, -haven’t upgraded their infrastructure.

- -

You see this in the picture below. Earthlink supports older -un-encrypted “POP3” (for fetching email from the server), but not -the new encrypted POP3 over SSL. Conversely, GMail doesn’t support -the older un-encrypted stuff (even if you wanted it to), but only -the newer encrypted version.

-
- -

Gogo is far from perfect, but it certainly wasn’t at fault in this case.

- -

Update: Like a lot of you, I’m not even sure I buy the whole story. Whole thing seems fishy.

- - - - ]]>
-
- Google, Facebook, Twitter, and Microsoft Plan to Support Apple - - - - tag:daringfireball.net,2016:/linked//6.32167 - 2016-02-25T22:56:47Z - 2016-02-25T22:56:48Z - - John Gruber - http://daringfireball.net/ - - Deepa Seetharaman and Jack Nicas, reporting for the WSJ:

- -
-

Several tech companies, including Google parent Alphabet Inc., -Facebook Inc. and Microsoft Corp., plan to file a joint motion -supporting Apple Inc. in its court fight against the Justice -Department over unlocking an alleged terrorist’s iPhone, according -to people familiar with the companies’ plans.

- -

At least one other tech company plans to be included in a joint -amicus brief next week generally supporting Apple’s position that -unlocking the iPhone would undermine tech firms’ efforts to -protect their users’ digital security, these people said. Twitter -Inc. also plans to support Apple in a motion, though it is unclear -if it will join the combined filing, another person familiar said.

- -

Microsoft President and Chief Legal Officer Brad Smith told -Congress on Thursday that his company would file a motion -supporting Apple.

-
- -

Nice.

- - - - ]]>
-
- Apple’s Motion to Vacate FBI Order - - - - tag:daringfireball.net,2016:/linked//6.32166 - 2016-02-25T20:24:56Z - 2016-02-25T20:25:28Z - - John Gruber - http://daringfireball.net/ - - A clear, cogent read. I often shy away from reading legal motions because they’re so often written in dense legalese, but this one is clear.

- -

This stuck out to me:

- -
-

Congress knows how to impose a duty on third parties to facilitate -the government’s decryption of devices. Similarly, it knows -exactly how to place limits on what the government can require of -telecommunications carriers and also on manufacturers of telephone -equipment and handsets. And in CALEA, Congress decided not to -require electronic communication service providers, like Apple, to -do what the government seeks here. Contrary to the government’s -contention that CALEA is inapplicable to this dispute, Congress -declared via CALEA that the government cannot dictate to providers -of electronic communications services or manufacturers of -telecommunications equipment any specific equipment design or -software configuration.

- -

In the section of CALEA entitled “Design of features and systems -configurations,” 47 U.S.C. § 1002(b)(1), the statute says that it -“does not authorize any law enforcement agency or officer —

- -
-

(1) to require any specific design of equipment, facilities, - services, features, or system configurations to be adopted by - any provider of a wire or electronic communication service, - any manufacturer of telecommunications equipment, or any - provider of telecommunications support services.

- -

(2) to prohibit the adoption of any equipment, facility, service, - or feature by any provider of a wire or electronic - communication service, any manufacturer of telecommunications - equipment, or any provider of telecommunications support - services.

-
-
- -

What Apple is arguing is that the All Writs Act is intended only to fill the gaps covering scenarios not covered by other laws, but CALEA (the Communications Assistance for Law Enforcement Act) is a law that was passed specifically to cover exactly this sort of scenario. This strikes me as a very compelling argument.

- - - - ]]>
-
- Microsoft Will File Amicus Brief Supporting Apple - - - - tag:daringfireball.net,2016:/linked//6.32165 - 2016-02-25T18:59:14Z - 2016-02-25T18:59:15Z - - John Gruber - http://daringfireball.net/ - - Dina Bass, reporting for Bloomberg:

- -
-

Microsoft Corp. backs Apple Inc. in its fight with the U.S. -government over unlocking a terrorist’s iPhone, said President and -Chief Legal Officer Brad Smith.

- -

The company will file an amicus brief to support Apple next week, -Smith said at a congressional hearing to discuss the need for new -legislation to govern privacy, security and law enforcement in the -age of Internet-based cloud services.

-
- -

Nice.

- - - - ]]>
-
- Apple to Tighten iCloud Backup Encryption - - - - tag:daringfireball.net,2016:/linked//6.32164 - 2016-02-25T18:02:44Z - 2016-02-25T18:02:45Z - - John Gruber - http://daringfireball.net/ - - Tim Bradshaw, reporting for the Financial Times:

- -
-

Apple is working on new ways to strengthen the encryption of -customers’ iCloud backups in a way that would make it impossible -for the company to comply with valid requests for data from law -enforcement, according to people familiar with its plans.

- -

The move would bolster Apple customers’ security against hackers -but also frustrate investigators who are currently able to obtain -data from Apple’s servers through a court order. Apple has -complied with thousands of such orders in the past.

- -

Developing such technology is in some ways more complex than -adding the kind of device-level security that Apple introduced to -the iPhone in 2014 with its iOS 8 update.

- -

Building new protections that mean Apple no longer has access to -iCloud encryption keys may inconvenience some customers. Such a -change would most likely mean that customers who forget their -iCloud password may be left unable to access their photos, -contacts and other personal information that is backed up to -Apple’s systems.

-
- - - - ]]>
-
- The Dangerous All Writs Act Precedent in the Apple Encryption Case - - - - tag:daringfireball.net,2016:/linked//6.32163 - 2016-02-25T17:07:13Z - 2016-02-25T17:07:15Z - - John Gruber - http://daringfireball.net/ - - Amy Davidson, writing for The New Yorker:

- -
-

It is essential to this story that the order to Apple is not a -subpoena: it is issued under the All Writs Act of 1789, which says -that federal courts can issue “all writs necessary or appropriate -in aid of their respective jurisdictions and agreeable to the -usages and principles of law.” Read as a whole, this simply means -that judges can tell people to follow the law, but they have to do -so in a way that, in itself, respects the law. The Act was written -at a time when a lot of the mechanics of the law still had to be -worked out. But there are qualifications there: warnings about the -writs having to be “appropriate” and “agreeable,” not just to the -law but to the law’s “principles.” The government, in its use of -the writ now, seems to be treating those caveats as background -noise. If it can tell Apple, which has been accused of no -wrongdoing, to sit down and write a custom operating system for -it, what else could it do?

-
- -

Lost amid the technical debate over encryption is the legal debate over this incredibly broad application of the All Writs Act.

- - - - ]]>
-
- Twitter’s Missing Manual - - - - tag:daringfireball.net,2016:/linked//6.32162 - 2016-02-25T16:45:49Z - 2016-02-25T16:45:50Z - - John Gruber - http://daringfireball.net/ - - Eevee:

- -
-

Here, then, is a list of all the non-obvious things about Twitter -that I know. Consider it both a reference for people who aren’t up -to their eyeballs in Twitter, and an example of how these hidden -features can pile up. I’m also throwing in a couple notes on -etiquette, because I think that’s strongly informed by the shape -of the platform.

-
- - - - ]]>
-
- Sharp Accepts Foxconn Takeover Bid - - - - tag:daringfireball.net,2016:/linked//6.32161 - 2016-02-25T05:21:30Z - 2016-02-25T17:46:42Z - - John Gruber - http://daringfireball.net/ - - Huge news for both companies. Interesting for Apple, too.

- -

Update:

- -
-

A deal to take over Japanese electronics giant Sharp by Taiwanese -manufacturer Foxconn, has been thrown into question by a last -minute delay.

- -

Foxconn said it had received new information from Sharp which -needed to be clarified.

-
- -

Whoops.

- - - - ]]>
-
- The Next Step in iPhone Impregnability - - - - tag:daringfireball.net,2016:/linked//6.32160 - 2016-02-25T03:26:27Z - 2016-02-25T04:35:17Z - - John Gruber - http://daringfireball.net/ - - Matt Apuzzo and Katie Benner, reporting for the NYT:

- -
-

Apple engineers have already begun developing new security -measures that would make it impossible for the government to break -into a locked iPhone using methods similar to those now at the -center of a court fight in California, according to people close -to the company and security experts.

- -

If Apple succeeds in upgrading its security — and experts say it -almost surely will — the company would create a significant -technical challenge for law enforcement agencies, even if the -Obama administration wins its fight over access to data stored on -an iPhone used by one of the killers in last year’s San -Bernardino, Calif., rampage. The F.B.I. would then have to find -another way to defeat Apple security, setting up a new cycle of -court fights and, yet again, more technical fixes by Apple. […]

- -

Apple built its recent operating systems to protect customer -information. As its chief executive, Timothy D. Cook, wrote in a -recent letter to customers, “We have even put that data out of our -own reach, because we believe the contents of your iPhone are none -of our business.”

- -

But there is a catch. Each iPhone has a built-in troubleshooting -system that lets the company update the system software without -the need for a user to enter a password. Apple designed that -feature to make it easier to repair malfunctioning phones.

-
- -

The way the iPhone works today, when put into recovery mode you can restore the operating system without entering the device passcode. The only restriction is that the version of iOS to be installed must be properly signed by Apple.

- -

I just tried it here with my old iPhone 6, which had been turned off for weeks. I powered it up, but did not unlock it. I put it in recovery mode, and then updated it to iOS 9.3 beta 4. Then it restarted. Now it’s running iOS 9.3 beta 4, and I still have not unlocked it. All my data is still on the phone — but it’s running a new version of iOS, without my having unlocked it.

- -

What the FBI wants Apple to do is create (and sign) a new version of iOS that they can force the San Bernardino suspect’s phone to install as an update — and this new version of iOS will allow them to easily brute-force the passcode.

- -

I think what Apple is leaking here is that they’re going to change this (perhaps as soon as this year’s new iPhone 7), so that you can’t install a new version of iOS, even in recovery mode, without entering the device’s passcode. (I think they will also do the same for firmware updates to the code that executes on the Secure Enclave — it will require a passcode lock.)

- -

If you do a full restore, you can install a new version of the OS without the passcode, but this wipes the data. See also: Activation Lock, which allows you to bypass the passcode to completely wipe an iPhone, but requires you to sign into iCloud before you can use it.

- - - - ]]>
-
- Scalia in 1987: ‘The Constitution Sometimes Insulates the Criminality of a Few in Order to Protect the Privacy of Us All’ - - - - tag:daringfireball.net,2016:/linked//6.32159 - 2016-02-25T02:53:17Z - 2016-02-25T02:53:18Z - - John Gruber - http://daringfireball.net/ - - NYT report on a 6-3 Supreme Court decision in 1987:

- -
-

Justice Scalia’s opinion was forcefully denounced as an -unjustified obstacle to law enforcement in dissenting opinions by -Associate Justices Sandra Day O’Connor and Lewis F. Powell Jr. -Chief Justice Rehnquist joined in both of the dissents.

- -

Justice Scalia, however, said, “There is nothing new in the -realization that the Constitution sometimes insulates the -criminality of a few in order to protect the privacy of us -all.” […]

- -

Justice Scalia’s majority opinion today said that although the -search for weapons was lawful — a shot had just been fired through -the floor of the apartment, injuring a man below — the police were -not justified in moving the stereo components even slightly to -check the serial numbers without “probable cause” to believe they -were stolen. He thus affirmed a ruling by an Arizona appellate -court that the stereo components, which turned out to have been -stolen in an armed robbery, could not be used as evidence against -the occupant of the apartment.

- -

Associate Justice William J. Brennan Jr., the Court’s senior -member, who is its leading liberal, apparently assigned Justice -Scalia to write the majority opinion, which he joined. Under the -Supreme Court’s procedures, the Chief Justice assigns opinions -when he is in the majority. When the Chief Justice dissents, as -in the Arizona case, the senior member of the majority has -assignment power.

-
- -

Conservative judges, as a general rule, tend to side with law enforcement in search and seizure cases. Scalia was certainly a conservative, but by no means was he in lockstep with them.

- - - - ]]>
-
- ABC News Posts Extensive Interview With Tim Cook on FBI/iPhone Case - - - - tag:daringfireball.net,2016:/linked//6.32158 - 2016-02-25T00:59:51Z - 2016-02-25T02:17:14Z - - John Gruber - http://daringfireball.net/ - - Solid, thorough, and I think very fair interview by David Muir. Cook made his case about as well as it could be made — a passionate defense of civil liberties. It’s 30 minutes long and worth every minute of it.

- - - - ]]>
-
- Former Bush Administration Official Argues Supreme Court Should Count Scalia’s Vote in Pending Cases - - - - tag:daringfireball.net,2016:/linked//6.32157 - 2016-02-24T22:46:11Z - 2016-02-24T22:46:12Z - - John Gruber - http://daringfireball.net/ - - This is how we get from here to there.

- - - - ]]>
-
- David Ortiz Makes a Final Plea to Yankees Fans - - - - tag:daringfireball.net,2016:/linked//6.32156 - 2016-02-24T22:02:58Z - 2016-02-24T22:17:46Z - - John Gruber - http://daringfireball.net/ - - Kevin Kernan, writing for the NY Post:

- -
-

When Ortiz, 40, makes his final Yankee Stadium appearance on Sept. -29, this is what he wants, and it speaks volumes about Ortiz the -player, the competitor, the enemy, the star.

- -

“You know what I want most of all?’’ Big Papi told The Post on -Tuesday at JetBlue Park. “I would love it if the fans at Yankee -Stadium gave me a standing ovation.’’

- -

That’s what he wants, and that would be the perfect tribute to -Ortiz, who owns 503 home runs.

-
- -

I would wholeheartedly join in that ovation. Great player, great rival, and his retirement really marks the end of the epic Yankees-Sox rivalry from the early 2000s. I would expect appearances from Derek Jeter, Mariano Rivera, Jorge Posada, and Joe Torre. Just thinking about it makes me want to buy tickets.

- - - - ]]>
-
- Spotify Moves Infrastructure to Google Cloud Platform - - - - tag:daringfireball.net,2016:/linked//6.32155 - 2016-02-24T03:01:04Z - 2016-02-24T03:01:06Z - - John Gruber - http://daringfireball.net/ - - You heard it here first: this presages Google acquiring Spotify. (I heard it from Om Malik first.)

- - - - ]]>
-
- Was Pew’s Polling Question on the Apple/FBI Debate Misleading? - - - - tag:daringfireball.net,2016:/linked//6.32154 - 2016-02-23T22:16:51Z - 2016-02-23T22:16:52Z - - John Gruber - http://daringfireball.net/ - - Mike Masnick, writing for TechDirt:

- -
-

The question asked was

- -
-

As you may know, RANDOMIZE: [the FBI has said that accessing the -iPhone is an important part of their ongoing investigation into -the San Bernardino attacks] while [Apple has said that unlocking -the iPhone could compromise the security of other users’ -information] do you think Apple [READ; RANDOMIZE]?

- -

(1) Should unlock the iPhone (2) Should not unlock the iPhone (3) - Don’t Know.

-
- -

But that’s not the issue in this case!

- -

As noted in the past, when it’s possible for Apple to get access -to data, it has always done so in response to lawful court orders. -That’s similar to almost every other company as well. This case is -different because it’s not asking Apple to “unlock the iPhone.” -The issue is that Apple cannot unlock the iPhone and thus, the FBI -has instead gotten a court order to demand that Apple create an -entirely new operating system that undermines the safety and -security of iPhones, so that the FBI can hack into the iPhone. -That’s a really different thing.

-
- -

He makes a good point. But when it comes to public polling on an issue like this, you can’t expect the public to understand the technical issues. Ideally, yes, the language used by Pew would have been much more precise. But basically what they were asking is “Do you think Apple should do whatever the FBI wants them to do to get the information from the San Bernardino suspect’s iPhone?” For polling purposes, I don’t think it matters much what “whatever” is.

- -

It’s true that if phrased differently, it’s quite possible you’d get a polling showing more support for Apple. But the bottom line is that a lot of Americans think Apple should just do what the FBI is asking them to do.

- - - - ]]>
-
- On Ribbons and Ribbon Cutters - - - - tag:daringfireball.net,2016:/linked//6.32153 - 2016-02-23T22:00:23Z - 2016-02-23T22:00:24Z - - John Gruber - http://daringfireball.net/ - - Jonathan Zdziarski (who has been killing it with his analysis of the Apple/FBI fight):

- -
-

With most non-technical people struggling to make sense of the -battle between FBI and Apple, Bill Gates introduced an excellent -analogy to explain cryptography to the average non-geek. Gates -used the analogy of encryption as a “ribbon around a hard drive”. -Good encryption is more like a chastity belt, but since Farook -decided to use a weak passcode, I think it’s fair here to call it -a ribbon. In any case, let’s go with Gates’s ribbon analogy. […]

- -

Instead of cutting the ribbon, which would be a much simpler task, -FBI is ordering Apple to invent a ribbon cutter — a forensic tool -capable of cutting the ribbon for FBI, and is promising to use it -on just this one phone. In reality, there’s already a line -beginning to form behind Comey should he get his way.

-
- - - - ]]>
-
- Apple to Restore UI Navigation With Pencil in Next iOS 9.3 Beta - - - - tag:daringfireball.net,2016:/linked//6.32152 - 2016-02-23T21:37:34Z - 2016-02-23T22:26:45Z - - John Gruber - http://daringfireball.net/ - - That didn’t take long. Apple, in a statement to iMore and a few other publications:

- -
-

Apple Pencil has been a huge hit with iPad Pro users, who love it -for drawing, annotating and taking notes,” an Apple spokesperson -told iMore. “We believe a finger will always be the primary way -users navigate on an iPad, but we understand that some customers -like to use Apple Pencil for this as well and we’ve been working -on ways to better implement this while maintaining compatibility -during this latest beta cycle. We will add this functionality back -in the next beta of iOS 9.3.

-
- -

One thing I take away from the vocal reaction to this: the Apple Pencil and iPad Pro have passionate users.

- - - - ]]>
-
- Apple vs. FBI: ‘Just This Once’? - - - - tag:daringfireball.net,2016:/linked//6.32151 - 2016-02-23T21:20:18Z - 2016-02-23T21:20:19Z - - John Gruber - http://daringfireball.net/ - - Julian Sanchez, writing for Just Security:

- -
-

Consider: Possibly the next iPhone simply eliminates Apple’s -ability to assist in any way. But it’s hard to imagine a -scenario where the designer and key-holder for a device designed -to be used by normal humans can do literally nothing, at the -margin, to assist an attacker. That means every improvement in -device security involves a gamble: Maybe the cost of developing -new ways to attack the newly hardened device becomes so high that -the courts recognize it as an “undue burden” and start quashing -(or declining to issue) All Writs Act orders to compel hacking -assistance. Maybe. But Apple is a very large, very rich company, -and much of the practical “burden” comes from the demands of -complying securely and at scale. The government will surely -continue arguing in future cases that the burden of complying -just this one time are not so great for a huge tech company like -Apple. (And, to quote The Smiths, they’ll never never do it -again — of course they won’t; not until the next time.)

-
- -

Sanchez makes an interesting point here about Apple being disincentivized from improving iPhone security if they lose this case. Imagine if Apple made safes, but the government could compel them to crack their own safes under warrant. The harder they make these safes to crack, the more work they bring upon themselves when compelled to crack them.

- -

I don’t think Apple would succumb to that and stop improving their device security, but it shows what an untenable position the government is trying to put Apple in. The only easy way out for Apple, if they lose, is to stop making iPhones truly secure.

- - - - ]]>
-
- High-Profile Attorney Ted Olson Joins Apple’s Fight Against FBI Terror Probe - - - - tag:daringfireball.net,2016:/linked//6.32150 - 2016-02-23T20:49:09Z - 2016-02-23T21:11:40Z - - John Gruber - http://daringfireball.net/ - - Taylor Goldenstein, reporting for the LA Times:

- -
-

Olson and Theodore J. Boutrous Jr. are the attorneys of record -representing Apple, according to a court filing. Boutrous and -Olson worked together to fight California’s previous ban on -same-sex marriage.

- -

Olson is best known for successfully arguing on behalf of George -W. Bush in the Supreme Court case Bush vs. Gore, which decided the -2000 presidential election, and for challenging California’s -Proposition 8, the measure that banned gay marriage, before the -Supreme Court.

-
- -

Olson is truly an extraordinary figure, both in terms of his career (winning landmark cases for conservatives, like Bush v. Gore and Citizens United; then winning the case that legalized gay marriage nationwide), and his personal life (his wife was a passenger on the plane that crashed into the Pentagon on 9/11).

- - - - ]]>
-
- iOS 9.3 Betas Remove the Ability to Navigate iPad UI With Apple Pencil - - - - tag:daringfireball.net,2016:/linked//6.32149 - 2016-02-23T19:24:01Z - 2016-02-23T21:39:01Z - - John Gruber - http://daringfireball.net/ - - Serenity Caldwell, at iMore:

- -
-

Unfortunately, whether by bug or intentional design, the Pencil’s -navigational prowess appears to have vanished in the iOS 9.3 -public betas. With 9.3, you can no longer scroll or manipulate -text; the only places the Pencil works are on canvas or when -pressing digital buttons.

- -

Normally, I don’t write about beta bugs and features, because it’s -a beta: There are always bugs, and features change. But this -functionality is important enough that I wanted to talk about it -before Apple submits its final 9.3 release. It could be a bug, -yes: But several betas in, we’ve seen fixes for Smart Connector -keyboards and new features, and the Pencil remains crippled. Which -makes me think, more and more, that this is a conscious decision -on the part of Apple’s engineering team. (I did reach out to the -company about the issue, and will update if and when I receive a -response.)

-
- -

Myke Hurley and CGP Grey talk about this on the latest episode of their podcast, Cortex. Grey says:

- -
-

Sources in the know confirm that removing the functionality of the -Apple Pencil is a decision inside of Apple. It is not a bug they -have overlooked for three betas. It is a decision.

-
- -

My only guess as to why Apple would change this is that they want to enable you to scroll/pan (with your finger) while drawing/marking-up with the Pencil. If so, the mistake wasn’t making this change in iOS 9.3 — the mistake was allowing the Pencil to control the UI in the first place.

- -

I hate to say it, but now that iPad Pro users have gotten used to using the Pencil to navigate the UI, maybe it should be a setting? Maybe under Accessibility? Grey, for example, says using the Pencil to navigate the UI helps him avoid RSI pain.

- -

Update, two hours later: Apple has told The Verge that UI navigation via Pencil will return in the next iOS 9.3 beta.

- - - - ]]>
-
- Bill Gates Breaks Ranks Over FBI Apple Request - - - - tag:daringfireball.net,2016:/linked//6.32148 - 2016-02-23T19:00:59Z - 2016-02-23T19:45:52Z - - John Gruber - http://daringfireball.net/ - - Stephen Foley and Tim Bradshaw, writing for The Financial Times:

- -
-

“This is a specific case where the government is asking for -access to information. They are not asking for some general -thing, they are asking for a particular case,” Mr Gates told the -Financial Times.

- -

“It is no different than [the question of] should anybody ever -have been able to tell the phone company to get information, -should anybody be able to get at bank records. Let’s say the bank -had tied a ribbon round the disk drive and said, ‘Don’t make me -cut this ribbon because you’ll make me cut it many times’.”

-
- -

Gates is so smart — surely he understands that if the FBI prevails, this will set precedent that will be used again and again. It seems to me he’s arguing that we should not be allowed to have devices protected by strong encryption.

- -

Update: Gates said today he thinks the FT mischaracterized his position, but I’m not really seeing it. He certainly isn’t siding with Apple — his stance seems, at best, lukewarm, like Sundar Pichai’s.

- - - - ]]>
-
- Poll Shows More Support for Justice Department Than for Apple - - - - tag:daringfireball.net,2016:/linked//6.32147 - 2016-02-23T18:54:44Z - 2016-02-24T03:22:36Z - - John Gruber - http://daringfireball.net/ - - Pew Research Center:

- -
-

As the standoff between the Department of Justice and Apple Inc. -continues over an iPhone used by one of the suspects in the San -Bernardino terrorist attacks, 51% say Apple should unlock the -iPhone to assist the ongoing FBI investigation. Fewer Americans -(38%) say Apple should not unlock the phone to ensure the security -of its other users’ information; 11% do not offer an opinion on -the question.

- -

News about a federal court ordering Apple to unlock the suspect’s -iPhone has registered widely with the public: 75% say they have -heard either a lot (39%) or a little (36%) about the situation.

-
- -

This is exactly why Apple’s stance on this issue is so commendable. They’re doing what they believe to be right, even though it is unpopular.

- - - - ]]>
-
- WSJ: ‘Justice Department Seeks to Force Apple to Extract Data From About 12 Other iPhones’ - - - - tag:daringfireball.net,2016:/linked//6.32146 - 2016-02-23T18:53:40Z - 2016-02-23T18:53:41Z - - John Gruber - http://daringfireball.net/ - - Devlin Barrett, reporting for the WSJ:

- -
-

The Justice Department is pursuing court orders to make Apple Inc. -help investigators extract data from iPhones in about a dozen -undisclosed cases around the country, in disputes similar to the -current battle over a terrorist’s locked phone, according to a -newly-unsealed court document.

- -

The other phones are evidence in cases where prosecutors have -sought, as in the San Bernardino, Calif., terror case, to use an -18th-century law called the All Writs Act to compel the company to -help them bypass the passcode security feature of phones that may -hold evidence, according to a letter from Apple which was unsealed -in Brooklyn federal court Tuesday. […]

- -

The letter doesn’t describe the specific types of criminal -investigations related to those phones, but people familiar with -them said they don’t involve terrorism cases. The 12 cases remain -in a kind of limbo amid the bigger, more confrontational legal -duel between the government and the company over an iPhone seized -in the terror case in California, these people said.

-
- -

But it’s really just about that one, single iPhone in the San Bernardino case.

- - - - ]]>
-
- ‘Absolutely Right’ - - - - tag:daringfireball.net,2016:/linked//6.32145 - 2016-02-23T18:36:27Z - 2016-02-23T18:39:51Z - - John Gruber - http://daringfireball.net/ - - Katie Benner and Matt Apuzzo, reporting for the NYT on whether the FBI’s request for Apple to unlock the San Bernardino shooter’s iPhone will open the door to more such requests:

- -
-

In a note posted to its website on Monday, Apple reiterated that -the government’s request seems narrow but really isn’t. “Law -enforcement agents around the country have already said they have -hundreds of iPhones they want Apple to unlock if the F.B.I. wins -this case,” the company said.

- -

To that point, the New York City police commissioner, William J. -Bratton, and the Manhattan district attorney, Cyrus R. Vance Jr., -criticized Apple after it refused to comply with the court order -and said that they currently possessed 175 iPhones that they could -not unlock.

- -

Charlie Rose recently interviewed Mr. Vance and asked if he would -want access to all phones that were part of a criminal proceeding -should the government prevail in the San Bernardino case.

- -

Mr. Vance responded: “Absolutely right.”

-
- - - - ]]>
-
- Mark Zuckerberg Stole Samsung’s Galaxy S7 Show - - - - tag:daringfireball.net,2016:/linked//6.32144 - 2016-02-23T03:46:31Z - 2016-02-23T03:46:32Z - - John Gruber - http://daringfireball.net/ - - Interesting marriage of convenience. Samsung has hardware but no interesting software. Facebook has interesting software but no hardware.

- - - - ]]>
-
- MDM Software Would Have Unlocked San Bernardino Shooter’s iPhone - - - - tag:daringfireball.net,2016:/linked//6.32143 - 2016-02-23T01:18:25Z - 2016-02-23T01:18:27Z - - John Gruber - http://daringfireball.net/ - - CBS News:

- -
-

If the technology, known as mobile device management, had been -installed, San Bernardino officials would have been able to -remotely unlock the iPhone for the FBI without the theatrics of a -court battle that is now pitting digital privacy rights against -national security concerns.

- -

The service costs $4 per month per phone.

- -

Instead, the only person who knew the unlocking passcode for the -phone is the dead gunman, Syed Farook, who worked as an inspector -in the county’s public health department.

-
- -

I had assumed they weren’t using MDM, but it’s good to have confirmation.

- - - - ]]>
-
- FBI Director James Comey Publishes Op-Ed on Apple/Encryption Case - - - - tag:daringfireball.net,2016:/linked//6.32141 - 2016-02-22T21:52:48Z - 2016-02-22T21:52:49Z - - John Gruber - http://daringfireball.net/ - - James Comey, in a brief op-ed published last night by Lawfare:

- -
-

The San Bernardino litigation isn’t about trying to set a -precedent or send any kind of message. It is about the victims and -justice. Fourteen people were slaughtered and many more had their -lives and bodies ruined. We owe them a thorough and professional -investigation under law. That’s what this is. The American people -should expect nothing less from the FBI.

-
- -

It is very difficult to take Comey’s opening sentence seriously. Everyone — on both sides of the issues — knows that this is about setting precedent.

- -
-

The particular legal issue is actually quite narrow. The relief we -seek is limited and its value increasingly obsolete because the -technology continues to evolve. We simply want the chance, with a -search warrant, to try to guess the terrorist’s passcode without -the phone essentially self-destructing and without it taking a -decade to guess correctly. That’s it. We don’t want to break -anyone’s encryption or set a master key loose on the land. I hope -thoughtful people will take the time to understand that. Maybe the -phone holds the clue to finding more terrorists. Maybe it doesn’t. -But we can’t look the survivors in the eye, or ourselves in the -mirror, if we don’t follow this lead.

-
- -

This is a purely emotional appeal. By Comey’s logic here, FBI agents should be considered above the law, able to pursue any and every avenue possible in the pursuit of information in a case with high stakes. That’s not how our system works. We are governed by the rule of law. Encryption is legal.

- -

Ultimately, that is where Comey and the FBI are going to take this. They’re going to try to make strong encryption illegal.

- - - - ]]>
-
- In Internal Email, Apple CEO Tim Cook Says Refusal to Unlock iPhone Is an Issue of Civil Liberties - - - - tag:daringfireball.net,2016:/linked//6.32140 - 2016-02-22T21:07:23Z - 2016-02-22T21:07:24Z - - John Gruber - http://daringfireball.net/ - - Tim Cook, in a company-wide memo:

- -
-

Apple is a uniquely American company. It does not feel right to be -on the opposite side of the government in a case centering on the -freedoms and liberties that government is meant to protect.

- -

Our country has always been strongest when we come together. We -feel the best way forward would be for the government to -withdraw its demands under the All Writs Act and, as some in -Congress have proposed, form a commission or other panel of -experts on intelligence, technology and civil liberties to -discuss the implications for law enforcement, national security, -privacy and personal freedoms. Apple would gladly participate in -such an effort.

-
- - - - ]]>
-
- Apple Publishes FAQ on Their Fight Against the FBI - - - - tag:daringfireball.net,2016:/linked//6.32139 - 2016-02-22T21:06:12Z - 2016-02-22T21:06:13Z - - John Gruber - http://daringfireball.net/ - - Cogent.

- - - - ]]>
-
- - - -tag:daringfireball.net,2016:/feeds/sponsors//11.32142 - - Daring Fireball Department of Commerce - - -2016-02-22T20:11:01-05:00 -2016-02-22T20:11:03-05:00 - -Everybody does research. Whether you investigate gravitational waves, do fact-finding for blog posts, study the genetics of Rhagoletis pomonella, or design the next revolutionary cupcake recipe, you are a researcher.

- -

Research needs great tools — that’s where we come in! We build innovative Mac and iOS apps for researchers, that even got us several Apple Design Awards. Today, we present our latest creations: Manuscripts and Findings.

- -
    -
  • Manuscripts is a writing tool that helps you concentrate on your story. Outline, plan and edit your project, insert figures, tables and math, then format citations using a killer workflow. Import and export of Markdown, Word, LaTeX and HTML is included.

  • -
  • Findings is a lab notebook app that helps you keep a journal of your research, connected to notes, photos and files. Plan your week, track progress, and share your findings with your colleagues… or the world.

  • -
- -

Try the free basic versions, and use coupon DARINGFIREBALL for a special discount on the unlimited versions, this week only. We hope you’ll like Manuscripts & Findings!

- -]]>
-[Sponsor] Manuscripts and Findings: Your Research Is Ready for a Big Upgrade
- The Talk Show: ‘iTools or Whatever’ - - - - tag:daringfireball.net,2016:/linked//6.32138 - 2016-02-21T23:15:40Z - 2016-02-21T23:15:43Z - - John Gruber - http://daringfireball.net/ - - For your enjoyment, a new episode of my podcast, with special guest Jim Dalrymple. Topics include the Apple/FBI legal showdown, the debate over Apple software quality, and more.

- -

Sponsored by:

- -
    -
  • Squarespace: Build it beautiful. Use code GRUBER for 10% off your first order.
  • -
  • Fracture: Photos printed in vivid color directly on glass. Use promo code TALKSHOW10 for 10% off your first order.
  • -
  • Harry’s: An exceptional shave at a fraction of the price. Use code TALKSHOW for $5 off your first purchase.
  • -
- - - - ]]>
-
- - - - tag:daringfireball.net,2016://1.32137 - 2016-02-21T22:05:40Z - 2016-02-21T22:45:23Z - - John Gruber - http://daringfireball.net/ - -The key point is that you do not have to unlock an iPhone to have it back up to iCloud. But a locked iPhone can’t back up to iCloud if the associated Apple ID password has been changed.

-]]>
- The latest news in the Apple-FBI legal fight has resulted in much confusion. John Paczkowski, reporting for BuzzFeed:

- -
-

The FBI has claimed that the password was changed by someone at -the San Bernardino Health Department. Friday night, however, -things took a further turn when the San Bernardino County’s -official Twitter account stated, “The County was working -cooperatively with the FBI when it reset the iCloud password at -the FBI’s request.”

- -

County spokesman David Wert told BuzzFeed News on Saturday -afternoon the tweet was an authentic statement, but he had nothing -further to add.

- -

The Justice Department did not respond to requests for comment on -Saturday; an Apple spokesperson said the company had no additional -comment beyond prior statements.

-
- -

Here is what the FBI wrote in its legal motion, in a footnote on the four ways Apple suggested they obtain the data they seek:

- -
-

(3) to attempt an auto-backup of the SUBJECT DEVICE with the - related iCloud account (which would not work in this case - because neither the owner nor the government knew the password - the iCloud account, and the owner, in an attempt to gain - access to some information in the hours after the attack, was - able to reset the password remotely, but that had the effect - of eliminating the possibility of an auto-backup);

-
- -

To unpack this, the “owner” is not Syed Farook, the shooter. The iPhone at the center of this was supplied by Farook’s employer, the San Bernardino County Department of Public Health. They are the “owner”. The “government” is the federal government: the FBI and the Department of Justice.

- -

The iPhone had been configured to back up to iCloud. However, at the time of the attack, it had not been backed up to iCloud for six weeks. Under warrant, Apple supplied the FBI with the data from that six-week-old backup. The FBI (for obvious reasons) would like the most recent six weeks of data from the phone, too.1

- -

iCloud backups are triggered automatically when the phone is (a) on a known Wi-Fi network, and (b) plugged-in to power. Apple’s suggestion to the FBI was that if they took the iPhone to Farook’s office and plugged it in, it might trigger a backup. If that had worked, Apple could supply the FBI with the contents of that new backup, including the most recent six weeks of data.

- -

It is not clear to me from any of the reports I have read why the iPhone had not been backed up in six weeks. It’s possible that Farook had disabled iCloud backups, in which case this whole thing is moot.2 But it’s also possible the only reason the phone hadn’t been backed up in six weeks is that it had not been plugged-in while on a known Wi-Fi network in six weeks. The phone would have to be unlocked to determine this, and the whole point of this fight is that the phone can’t be unlocked.

- -

The FBI screwed this up by directing the San Bernardino County Department of Public Health to reset Farook’s Apple ID password. They did not, and apparently could not, change anything on the phone itself. But once they reset the Apple ID password, the phone could not back up to iCloud, because the phone needed to be updated with the newly-reset Apple ID password — and they could not do that because they can’t unlock the phone.

- -

The key point is that you do not have to unlock an iPhone to have it back up to iCloud. But a locked iPhone can’t back up to iCloud if the associated Apple ID password has been changed.

- -

Again, there are two password-type things at play here. The Apple ID (iCloud) password, and the four-digit device passcode locking the iPhone. The county, at the behest of the FBI, reset the Apple ID password. This did not allow them to unlock the iPhone, and, worse, it prevented the iPhone from initiating a new backup to iCloud.

- -

How did the county reset Farook’s Apple ID password? We don’t know for sure, but the most likely answer is that if his Apple ID was his work-issued email account, then the IT department at the county could go to iforgot.apple.com, enter Farook’s work email address, and then access his email account to click the confirmation URL to reset the password.

- -

In short:

- -
    -
  • The data the FBI claims to want is on Farook’s iPhone.
  • -
  • They already have access to his iCloud account.
  • -
  • They might have been able to transfer the data on his iPhone to his iCloud account via an automated backup, but they can’t because they reset his Apple ID (iCloud) password.
  • -
- -

The only possible explanations for this are incompetence or dishonesty on the part of the FBI. Incompetence, if they didn’t realize that resetting the Apple ID password could prevent the iPhone from backing up to iCloud. Dishonesty, if they directed the county to do this knowing the repercussions, with the goal of setting up this fight to force Apple to create a back door for them in iOS. I’m not sure which to believe at this point. I’d like to know exactly when this directive to reset the Apple ID password was given — ” in the hours after the attack” leaves a lot of wiggle room.

- -
-
-
    -
  1. -

    Much (or all?) of the data stored on Apple’s iCloud backup servers is not encrypted. Or, if it is encrypted, it is encrypted in a way that Apple can decrypt. Apple has a PDF that describes the information available to U.S. law enforcement from iCloud, but to me it’s not clear exactly what is available under warrant. I would bet a large sum of money that Apple is hard at work on an iCloud backup system that does store data encrypted in a way that Apple cannot read it without the user’s Apple ID password. ↩︎

    -
  2. -
  3. -

    Another possibility: Farook’s iCloud storage was full. If this were the case, presumably Apple could have granted his account additional storage to allow a fresh backup to occur. But again, this became moot as soon as the county reset the Apple ID password at the behest of the FBI. ↩︎︎

    -
  4. -
-
- - - - ]]>
- ★ On the San Bernardino Suspect’s Apple ID Password Reset
- White House Petition to Side With Apple in FBI Fight - - - - tag:daringfireball.net,2016:/linked//6.32136 - 2016-02-21T21:38:07Z - 2016-02-21T21:38:09Z - - John Gruber - http://daringfireball.net/ - - I don’t have high hopes for this (the Obama administration seems hopelessly tied to law enforcement on this subject), but I signed:

- -
-

The FBI, is demanding that Apple build a “backdoor” to bypass -digital locks protecting consumer information on Apple’s popular -iPhones.

- -

We the undersigned, oppose this order, which has implications far -beyond the legal case at hand.

-
- - - - ]]>
-
- New York Times Publishes Report on iPhone Security and China - - - - tag:daringfireball.net,2016:/linked//6.32135 - 2016-02-20T22:36:43Z - 2016-02-20T22:39:02Z - - John Gruber - http://daringfireball.net/ - - Katie Benner and Paul Mozer, reporting for the NYT and revisiting the topic excised from a report earlier this week:

- -
-

In China, for example, Apple — like any other foreign company -selling smartphones — hands over devices for import checks by -Chinese regulators. Apple also maintains server computers in -China, but Apple has previously said that Beijing cannot view the -data and that the keys to the servers are not stored in China. In -practice and according to Chinese law, Beijing typically has -access to any data stored in China.

- -

If Apple accedes to American law enforcement demands for opening -the iPhone in the San Bernardino case and Beijing asks for a -similar tool, it is unlikely Apple would be able to control -China’s use of it. Yet if Apple were to refuse Beijing, it would -potentially face a battery of penalties.

- -

Analysts said Chinese officials were pushing for greater control -over the encryption and security of computers and phones sold in -the country, though Beijing last year backed off on some proposals -that would have required foreign companies to provide encryption -keys for devices sold in the country after facing pressure from -foreign trade groups.

- -

“People tend to forget the global impact of this,” said Raman Jit -Singh Chima, policy director at Access Now, a nonprofit that works -for Internet freedoms. “The reality is the damage done when a -democratic government does something like this is massive. It’s -even more negative in places where there are fewer freedoms.”

-
- -

Another way to look at this is a choice between the lesser of two evils. Is it a bad thing if law enforcement loses access to the contents of cell phones as state of the art for security increases? Yes. But it would be far, far worse — for entirely different reasons — if we eliminate true security by mandating back doors.

- - - - ]]>
-
- San Bernardino Officials: Apple ID Password for Terrorist’s iPhone Reset at FBI Request - - - - tag:daringfireball.net,2016:/linked//6.32134 - 2016-02-20T22:14:38Z - 2016-02-20T23:45:31Z - - John Gruber - http://daringfireball.net/ - - This story keeps getting weirder. John Paczkowski, at BuzzFeed:

- -
-

The FBI has claimed that the password was changed by someone at -the San Bernardino Health Department. Friday night, however, -things took a further turn when the San Bernardino County’s -official Twitter account stated, “The County was working -cooperatively with the FBI when it reset the iCloud password at -the FBI’s request.”

- -

County spokesman David Wert told BuzzFeed News on Saturday -afternoon the tweet was an authentic statement, but he had nothing -further to add.

- -

The Justice Department did not respond to requests for comment on -Saturday; an Apple spokesperson said the company had no additional -comment beyond prior statements.

-
- -

The additional wrinkle here is that when the FBI first revealed this, in this footnote (screenshot) of their legal motion (whole motion linked above, on “claimed”), they strongly implied that the San Bernardino Health Department did this on their own, like they were a bunch of yokels who panicked and did the wrong thing. Instead, it turns out, they were following the FBI’s instructions.

- -

The FBI says this happened “in the hours after the attack”. My question: How many hours?

- - - - ]]>
-
- DevMate by MacPaw - - - - tag:daringfireball.net,2016:/linked//6.32133 - 2016-02-20T00:23:57Z - 2016-02-20T00:23:58Z - - John Gruber - http://daringfireball.net/ - - My thanks to MacPaw for sponsoring this week’s DF RSS feed to announce that their developer platform DevMate is now available free of charge. DevMate is a single SDK that provides a slew of back-end services for Mac developers: in-app purchasing, software licensing, update delivery, crash reports, user feedback, and more. Plus real-time analytics, with sales and downloads, are available from DevMate’s dashboard.

- -

Among the indie Mac developers using DevMate for their apps are MacPaw themselves (for CleanMyMac), Smile Software, and Realmac. It’s a robust, dependable solution for developers who want to sell their Mac apps outside the App Store.

- - - - ]]>
-
- More Mac App Store Certificate Problems - - - - tag:daringfireball.net,2016:/linked//6.32132 - 2016-02-20T00:12:10Z - 2016-02-20T00:19:07Z - - John Gruber - http://daringfireball.net/ - - Lost amid the FBI/iPhone encryption hubbub was another bad week for the Mac App Store — apps just stopped launching, with the only solution being to delete the app(s) and re-install from the store. Michael Tsai (as usual) compiled a thorough roundup of information and commentary.

- - - - ]]>
-
- - - - tag:daringfireball.net,2016://1.32054 - 2016-02-04T01:28:15Z - 2016-02-04T16:33:43Z - - John Gruber - http://daringfireball.net/ - -Maybe we expect too much. But Apple’s hardware doesn’t have little problems like this.

-]]>
- Following up on Walt Mossberg’s column regarding the quality of Apple’s first-party apps, Jim Dalrymple writes:

- -
-

I understand that Apple has a lot of balls in the air, but they -have clearly taken their eye off some of them. There is absolutely -no doubt that Apple Music is getting better with each update to -the app, but what we have now is more of a 1.0 version than what -we received last year.

- -

Personally, I don’t care much about all the celebrities that Apple -can parade around — I care about a music service that works. -That’s it.

- -

If Apple Music (or any of the other software that has -problems) was the iPhone, it would never have been released in -the state it was.

-
- -

Software and hardware are profoundly different disciplines, so it’s hard to compare them directly. But it seems obvious to me that Apple, institutionally, has higher standards for hardware design and quality than it does for software.

- -

Maybe this is the natural result of the fact hardware standards must be high, because they can’t issue “hardware updates” over the air like they can with software. But the perception is now widespread that the balance between Apple’s hardware and software quality has shifted in recent years. I see a lot of people nodding their heads in agreement with Mossberg and Dalrymple’s pieces today.

- -

We went over this same ground a year ago in the wake of Marco Arment’s “Apple Has Lost the Functional High Ground”, culminating in a really interesting (to me at least) discussion with Phil Schiller at my “Live From WWDC” episode of The Talk Show. That we’re still talking about it a year later — and that the consensus reaction is one of agreement — suggests that Apple probably does have a software problem, and they definitely have a perception problem.

- -

I’ll offer a small personal anecdote. Overall I’ve had great success with iCloud Photo Library. I’ve got over 18,000 photos and almost 400 videos. And I’ve got a slew of devices — iPhones, iPads, and Macs — all using the same iCloud account. And those photos are available from all those devices. Except, a few weeks ago, I noticed that on my primary Mac, in Photos, at the bottom of the main “Photos” view, where it tells you exactly how many photos and videos you have, it said “Unable to Upload 5 Items”. Restarting didn’t fix it. Waiting didn’t fix it. And clicking on it didn’t do anything — I wanted to know which five items couldn’t be uploaded, and why. It seems to me that anybody in this situation would want to know those two things. But damned if Photos would tell me.

- -

Eventually, I found this support thread which suggested a solution: you can create a Smart Group in Photos using “Unable to upload to iCloud Photo Library” as the matching condition. Bingo: five items showed up. (Two of them were videos for which the original files couldn’t be found; three of them were duplicates of photos that were already in my library.)

- -

My little iCloud Photo Library syncing hiccup was not a huge deal — I was even lucky insofar as the two videos that couldn’t be found were meaningless. And I managed to find a solution. But it feels emblematic of the sort of nagging software problems people are struggling with in Apple’s apps. Not even the bug itself that led to these five items being unable to upload, but rather the fact that Photos knew about the problem but wouldn’t tell me the details I needed to fix it without my resorting to the very much non-obvious trick of creating a Smart Group to identify them. For me at least, “silent failure” is a big part of the problem — almost everything related to the whole discoveryd/mDNSresponder fiasco last year was about things that just silently stopped working.

- -

Maybe we expect too much from Apple’s software. But Apple’s hardware doesn’t have little problems like this.

- - - - ]]>
- ★ Apple’s App Problem
- - - - tag:daringfireball.net,2016://1.31994 - 2016-01-21T00:00:17Z - 2016-01-21T00:18:18Z - - John Gruber - http://daringfireball.net/ - -A year ago Apple sold 75 million iPhones in the fourth quarter of calendar 2015. There is no facility in the U.S. that can do that. There might not be anywhere in the world other than China that can operate at that sort of scale.

-]]>
- Arik Hesseldahl, writing for Recode on Donald Trump’s “we’re gonna get Apple to start building their damn computers and things in this country, instead of in other countries” campaign promise:

- -
-

Any honest presidential candidate regardless of party should say -clearly and indeed proudly that America doesn’t want these jobs to -come back. Final assembly jobs are low-skilled, low-paying -occupations; no American would wish to support a family on what -the jobs would pay. Workers at China’s Foxconn, which -manufacturers the iPhone, make about $402 per month after three -months of on-the-job probation. Even at the lowest minimum wage in -the U.S. — $5.15 an hour in Wyoming — American workers can’t -beat that.

-
- -

It’s not that simple. These jobs are certainly menial, but they’re not low-skill. As Tim Cook said on 60 Minutes:

- -
-

Charlie Rose: So if it’s not wages, what is it?

- -

Tim Cook: It’s skill. […]

- -

Charlie Rose: They have more skills than American workers? They -have more skills than —

- -

Tim Cook: Now — now, hold on.

- -

Charlie Rose: — German workers?

- -

Tim Cook: Yeah, let me — let me — let me clear, China put an -enormous focus on manufacturing. In what we would call, you and I -would call vocational kind of skills. The U.S., over time, began -to stop having as many vocational kind of skills. I mean, you can -take every tool and die maker in the United States and probably -put them in a room that we’re currently sitting in. In China, you -would have to have multiple football fields.

- -

Charlie Rose: Because they’ve taught those skills in their -schools?

- -

Tim Cook: It’s because it was a focus of them — it’s a focus of -their educational system. And so that is the reality.

-
- -

Wages are a huge factor, but for the sake of argument, let’s say Apple was willing to dip into its massive cash reserves and pay assembly line workers in the U.S. a good wage. Where would these U.S.-made iPhone be assembled? A year ago Apple sold 75 million iPhones in the fourth quarter of calendar 2014. There is no facility in the U.S. that can do that. There might not be anywhere in the world other than China that can operate at that sort of scale. That’s almost one million iPhones per day. 10 iPhones per second. Think about that.

- -

You can say, well, Apple could dig even deeper into its coffers and build such facilities. And train tens of thousands of employees. But why would they? Part of the marvel of Apple’s operations is that they can assemble and sell an unfathomable number of devices but they’re not on the hook for the assembly plants and facilities. When iPhones go the way of the iPod in 10 or 15 or 20 years, Apple doesn’t have any factories to close or convert for other uses. Foxconn does.

- -

The U.S. can’t compete with China on wages. It can’t compete on the size of the labor force. China has had a decades-long push in its education system to train these workers; the U.S. has not. And the U.S. doesn’t have the facilities or the proximity to the Asian component manufacturers.

- -

The only way Apple could ever switch to U.S. assembly and manufacturing would be if they automated the entire process — to build machines that build the machines. That, in fact, is what NeXT did while they were in the hardware business. But NeXT only ever sold about 50,000 computers total. Apple needed to assemble 35,000 iPhones per hour last year.

- -

So long as assembling these devices remains labor intensive, it has to happen in China. And if someday it becomes automated — if the machines are built by machines — by definition it’s not going to create manufacturing jobs.1

- -
-
-
    -
  1. -

    I do wonder about the purported Apple car. Would that be assembled in China, too? The U.S. does have automobile manufacturing expertise. And a car is so utterly unlike any product Apple has ever made that I feel like anything is possible. ↩︎

    -
  2. -
-
- - - - ]]>
- ★ Why Apple Assembles in China
- - - - tag:daringfireball.net,2015://1.31881 - 2015-12-11T21:19:40Z - 2015-12-15T00:38:58Z - - John Gruber - http://daringfireball.net/ - -Regarding Apple’s new Smart Battery Case for the iPhone 6/6S.

-]]>
- Joanna Stern tested Apple’s new Smart Battery Case for five days, and likes it a lot:

- -
-

Let’s get this out of the way: The bar for battery-case design is -extremely low. Most are chunky and made of black matte plastic, -requiring you to attach two pieces to your phone. You choose a -battery case for utility, not fashion.

- -

Apple’s Smart Battery Case, though still fairly unsightly, is -ahead of those. Bend back the top and slide in your phone. It -feels just like Apple’s smooth, soft-touch wraparound silicone -case, except… with a protruding, awkward battery on the back. The -battery juts out as if your phone will soon give birth to a -rectangular alien.

- -

Still, I’ll take it over all the ugly messes sold by Mophie, -Anker and others, especially since it provides better protection -for the phone. A lip curves just above the screen to prevent the -glass from hitting a hard surface and an interior lining provides -better shock absorption than hard plastic. Plus, the grippy -material is much easier to hold and doesn’t feel like it will -slip from my hands.

-
- -

The Verge’s Lauren Goode disagrees:

- -
-

Apple’s smart battery case is fine, then, if you want a softer -case or a “passive” battery charging experience, with zero control -over or understanding of how the case actually charges your phone. -Maybe that’s what Apple is hoping: that buyers of this thing will -slip it on and never take it off, charging their iPhones entirely -through the case’s Lightning port going forward, forgetting about -its big ol’ bump in the back. They will be pleased, finally, with -their iPhone 6’s or 6S’s battery life, and the memory of spending -an extra $99 for it, rather than having it just work that way in -the first place, will eventually fade away.

- -

It’s fine if you don’t want exterior indicator lights, or a even a -case that gives you a 0 to 100 percent charge. After all, this one -was designed for the iPhone, by the same company that made your -iPhone. For some people, that’s a big draw.

- -

In either case this will probably sell like hot cakes. It fits -nicely in holiday stockings. ’Tis the season. Just know that from -a pure performance and even a design perspective, Apple’s effort -is not the best you can get.

-
- -

(I can almost see her eyes rolling as she typed those italicized words in the second quoted paragraph.)

- -

Lewis Hilsenteger of Unbox Therapy best captured what most of us thought when we first saw it: “These things look weird.”

- -

That was certainly my first impression when I got mine Tuesday morning. The looks-like-it’s-pregnant-with-an-iPod-Touch design is certainly curious. I think to understand why it looks like this we have to ask why it even exists:

- -
    -
  • People who use their phones heavily — power users, if you will — struggle to get through a day on a single charge with the iPhone 6/6S.

  • -
  • The Plus models offer so much more battery life that getting through the day on a single charge isn’t a problem, even for power users who are on their phones all day long. But most people don’t want an iPhone that large.

  • -
  • Apple has long sold third-party battery cases in its stores, so they know how popular they are.

  • -
  • Existing battery cases all suffer from similar design problems, as outlined by Joanna Stern above. They make the entire device look and feel chunky, and most of them are built from materials that don’t feel good. None of them integrate in any way with the software on the iPhone, and most of them use micro USB instead of Lightning for charging the case.

  • -
  • Lastly, Apple claims the Smart Battery Case tackles a problem I wasn’t aware existed: that existing battery cases adversely affect cellular reception because they’re putting a battery between the phone’s antenna and the exterior of the case.

  • -
- -

So I think Apple’s priorities for the Smart Battery Case were as follows — and the order matters:

- -
    -
  1. Provides effective battery life equivalent to the iPhone 6S Plus.
  2. -
  3. Feels good in your hand.
  4. -
  5. Makes it easy and elegant to insert and remove the phone.
  6. -
  7. Works as a durable protective case.
  8. -
  9. Prevents the case’s battery from affecting cellular reception.
  10. -
  11. Looks good.
  12. -
- -

That “looks good” is last on the list is unusual for an Apple product, to say the least. Looking good isn’t always first on Apple’s list of priorities, but it’s seldom far from the top. But in this case it makes sense: Apple sells great-looking silicone and leather cases for people who aren’t looking for a battery case, and all existing third-party battery cases are clunky in some way.

- -

Ungainly though the case’s hump is, I can’t help but suspect one reason for it might be, counterintuitively, a certain vanity on the part of its designers. Not for the sake of the case itself, but for the iPhone. Third-party “thick from top to bottom” battery cases make it impossible to tell whether the enclosed phone is itself thick or thin. Apple’s Smart Battery Case makes it obvious that it’s a thin iPhone in a case which has a thick battery on the back. And I’ll say this for Apple: they are owning that hump. The hero photo of the case on the packaging is a face-on view of the back of the case.

- -

But I think the main reasons for this design are practical. The battery doesn’t extend to the top in order to accommodate the hinge design for inserting and removing the phone. Why it doesn’t extend to the bottom is a little less obvious. I suspect one reason is that that’s where the “passively coupling antenna” is.1 Extending the battery to cover it would defeat the purpose. Also, there’s a hand feel aspect to it — normally I rest the bottom of my iPhone on my pinky finger. With this case, I can rest the bottom ridge of the hump on my pinky, and it’s kind of nice. I also like putting my index finger atop the hump.

- -

So the Smart Battery Case looks weird. Typical battery cases look fat. Whether you prefer the weird look of the Smart Battery Case to the fat look of a typical case is subjective. Me, I don’t like the way any of them look. But after using the Smart Battery Case for three days, and having previously spent time using the thinnest available cases from Mophie, I feel confident saying Apple’s Smart Battery Case feels better when you’re holding it than any other battery case, both because of the material and its shape. It’s not even a close call. It also feels sturdier — this is the most protective iPhone case Apple has ever made, with rigid reinforced sides and a slightly higher lip rising above the touchscreen. The Smart Battery Case also clearly looks better from your own face-on perspective when using the phone. (Mophie’s cases look better than most, but they emboss an obnoxious “mophie” logotype on the front-facing chin. If Apple doesn’t print anything on the front face of the iPhone, why in the world would a case maker?)

- -

Patents, by the way, are a non-issue regarding the Smart Battery Case’s design. A well-placed little birdie who is perched in a position to know told me that Nilay Patel’s speculation that the unusual design was the byproduct of Apple trying to steer clear of patents held by Mophie (or any other company for that matter) are “absolute nonsense”. This birdie was unequivocal on the matter. Whether you like it, hate it, or are ambivalent about it, this is the battery case Apple wanted to make.

- -

My take is that the Smart Battery Case is an inelegant design, but it is solving a problem for which, to date, no one has created an elegant solution. Apple has simply chosen to make different severe trade-offs than the existing competition. In that sense, it is a very Apple-like product — like the hockey-puck mouse or the iMac G4.

- -

On Capacity, Simplicity, and the Intended Use Case

- -

Most battery cases have an on/off toggle switch, controlling when the case is actually charging the phone. The reason for this is that you can squeeze more from a battery case if you only charge the phone when it’s mostly depleted. Here’s a passage from Mophie’s FAQ page:

- -
-

When should I turn on my mophie case?

- -

To get the most charge out of your case, turn it on around 10%-20% -and keep the case charging without using it until your iPhone hits -80% battery life. From there, you can either wait until it gets -low again or top it off when the battery is less than 80%. Apple’s -batteries fast-charge to 80%, then switch to trickle charging for -the last 20%.

-
- -

Simplicity is a higher priority for Apple than fiddly control. If a peripheral can get by without an on/off switch, Apple is going to omit the switch. (Exhibit B: Apple Pencil.) The whole point of the Smart Battery Case is that you charge it up and put your iPhone in it and that’s it. Complaining about the lack of an on/off toggle or external charge capacity indicator lights on the Smart Battery Case reminds me of the complaints about the original iPhone omitting the then-ubiquitous green/red hardware buttons for starting and ending phone calls. Sure, there was a purpose to them, but in the end the simplification was worth it. If your iPhone is in the case, it’s charging. That’s it.

- -

Regarding the battery capacity of the case, here’s Lauren Goode, author of the aforelinked review for The Verge, on Twitter:

- -
-

A quick comparison for you: $99 Apple Battery Case 1877 mAh, -$100 Mophie Juice Pack Air 2750 mAh, $50 Incipio Offgrid Express -3000 mAh

-
- -

Nothing could better encapsulate the wrong way of looking at the Smart Battery Case than this tweet. The intended use of the Smart Battery Case is to allow prolonged, heavy use of an iPhone 6/6S throughout one day. In my testing, and judging by the reviews of others, its 1,877 mAh battery is enough for that. Adding a bigger battery would have just made it even heavier and more ungainly.

- -

And the very name of the Incipio Offgrid Express suggests that it is intended for an entirely different use case: traveling away from power for more than a day.

- -

Which in turn brings me to Tim Cook’s comments to Mashable’s Lance Ulanoff yesterday:

- -
-

Some also see the introduction of an Apple battery case as an -admission that battery life on the iPhone 6 and 6s isn’t all it -should be.

- -

Cook, though, said that “if you’re charging your phone every day, -you probably don’t need this at all. But if you’re out hiking and -you go on overnight trips… it’s kind of nice to have.”

-
- -

The Smart Battery Case would certainly help with an overnight hiking trip, but I think Cook was off-message here, because that scenario is really not what it was designed for. Big 5,000 mAh (or more) external battery chargers (or the highest capacity, extremely thick battery cases from third parties) are far better suited to that scenario than the Smart Battery Case. But Ulanoff’s preceding paragraph points to the marketing predicament inherent in a first-party Apple battery case: that it implies the built-in battery of the iPhone 6S is insufficient.

- -

The clear lesson is that it’s far better to give a phone more battery life by making the phone itself thicker and including a correspondingly thicker (and thus bigger) internal battery than by using any sort of external battery. After a few days using this case, my thoughts turn not to the Smart Battery Case itself but instead to my personal desire that Apple had made the 6/6S form factor slightly thicker. Not a lot thicker. Just a little — just enough to boost battery life around 15-20 percent or so.2 That wouldn’t completely alleviate the need for external batteries. But it would eliminate a lot of my need — my phone dies only a few times a year, but when it does, it almost invariably happens very late at night.

- -

I emphasized the word “personal” in the preceding paragraph because I realize my needs and desires are not representative of the majority. I think the battery life of the iPhone 6S as-is is sufficient for the vast majority of typical users. I suspect Cook went with the overnight hiking scenario specifically to avoid the implication that the built-in battery is insufficient. But the better explanation is that the built-in battery is insufficient for power users who use their iPhones far more than most people do.

- -

My Advice

- -

If you find yourself short on battery with your iPhone every day (or even most days), and you can’t make an adjustment to, say, put a charging dock on your desk or in your car to give your iPhone’s internal battery a periodic snack, then you should probably bite the bullet and switch to a 6S Plus. However bulky the Plus feels in your pocket and hands, it feels less bulky to me than the iPhone 6S with any battery pack. An iPhone 6S Plus, even with a normal case on it, weighs noticeably less than an iPhone 6S with the Smart Battery Case. If you need the extra battery capacity every day, you might as well get the Plus. (If you actually prefer the bigger Plus to the 4.7-inch devices, you’re in luck — you get the screen size you prefer, and a significantly longer-lasting battery. My advice here is for those who prefer the 4.7-inch size, other considerations aside.)

- -

That doesn’t describe me, however. On a typical day, my iPhone 6S seldom drops below 20 percent by the time I go to sleep. But when I’m traveling, I often need a portable battery of some sort. Cellular coverage can be spotty (which drains the battery), and when I’m away from home, I tend to do more (or even the entirety) of my daily computing on the iPhone. Conferences, in particular, can be dreadful on battery life. At WWDC my iPhone can drop to 50 percent by the time the keynote is over Monday morning.

- -

In recent years, rather than use a battery case, I’ve switched to carrying a portable external battery. My favorite for the past year or so is the $80 Mophie Powerstation Plus 2X. It’s relatively small, packs a 3,000 mAh capacity, and has built-in USB and Lightning cables. At conferences or for work travel, it’s easily stashed in my laptop bag, so my pockets aren’t weighed down at all, and my iPhone isn’t saddled with an unnatural case. If I do need to carry it in my pocket, it’s not too bad. It’s also easier to share with friends or family than a battery case. At night, I just plug the Powerstation into an AC adapter, and my iPhone into the Powerstation, and both devices get charged — no need for a separate charger or any additional cables.

- -

The big advantage to using a battery case instead of an external battery pack is that you can easily keep using your phone while it charges. That’s awkward, at best, while your phone is tethered by a cable to a small brick.

- -

If I were going to go back to using a battery case, there’s no question in my mind that I’d go with Apple’s. The only downside to it compared to Mophie’s (and the others — but I think Mophie is clearly the leader of the pack) is that it looks funny from the back. But to my eyes it doesn’t look that funny, and though third-party cases don’t look weird, they don’t look (or feel) good. In every other way, Apple’s Smart Battery Case wins: it’s all Lightning, so any Lightning peripherals you have will work, and there’s no need to pack a grody micro USB cable; it supplies more than enough additional power to get you through an active day; its unibody design makes it much easier to insert and remove the phone; and it feels much better in hand.

- -
-
-
    -
  1. -

    My understanding of how this “passively assistive antenna” works is that it takes the cellular signal and amplifies it as it passes through the case in a way that makes it easier for the iPhone’s antenna to “hear”. Sort of like the antenna equivalent of cupping your hand around your ear. I have no idea whether this is legit, or some sort of placebo marketing bullshit, but it would be interesting to see someone measure the cellular reception of (a) a naked iPhone 6S, (b) the same iPhone in a, say, Mophie battery case, and (c) the same iPhone in the Smart Battery Case. ↩︎

    -
  2. -
  3. -

    The iPhone 6 and 6S are actually 0.2mm thinner than their corresponding Plus models. That’s sort of crazy. The difference is barely perceptible, but if anything, the 6 and 6S should be a little thicker, not thinner, than the Plus models. ↩︎︎

    -
  4. -
-
- - - - ]]>
- ★ The Curious Case of the Curious Case
- - - - tag:daringfireball.net,2015://1.31795 - 2015-11-14T04:57:52Z - 2015-12-09T03:58:34Z - - John Gruber - http://daringfireball.net/ - -Take away every single iPhone sold — all of them — and Apple’s remaining business for the quarter was almost as big as Microsoft’s, bigger than Google’s, and more than four times the size of Facebook’s.

-]]>
- This piece by Bryan Clark for TheNextWeb caught my eye last weekend — “We’ve Reached — Maybe Passed — Peak Apple: Why the Narrative Needs to Change”:

- -
-

Last month, Apple’s latest earnings call announced its “most -successful year ever.” The numbers were reported, the stories -were spun and Wall Street basically anointed Apple the god of -capitalism.

- -

They’re all wrong.

-
- -

Apple wasn’t wrong — fiscal 2015 was Apple’s most successful year ever, by the objective measures of both revenue and profit. I suppose you can decide to define “most successful year ever” in terms of something else, like percentage growth or stock price gains, but revenue and profit are pretty fair measures.

- -

I missed it where “Wall Street basically anointed Apple the god of capitalism”. All I noticed was that Apple’s stock price went up about two percent the day after earnings were announced and has since fallen back to where it was before Q4 earnings were announced.

- -
-

The actual story, the story we should be telling, involves a -different narrative. Apple is the largest company in the world, -but success is fleeting. While the numbers are impressive, they -don’t come close to painting an accurate picture about how much -trouble Apple is really in.

- -

Apple’s rise under Steve Jobs was historic. Its fall under Tim -Cook is going to be much slower, more painful.

-
- -

The fall usually is more painful than the rise. Who writes a sentence like that?

- -

And if Apple’s fall under Cook is much slower than its rise under Steve Jobs, it’s going to take 20 or 30 years. Apple’s revival was long, slow, and relatively steady.

- -
-

Apple lives and dies by the iPhone. iPad sales are flat, -iPod’s are all but irrelevant, and while Mac sales are up, -they’re nowhere close to the workhorse that can continue to -carry Apple should they experience a downturn in iPhone sales. -There is no Plan B.

- -

One look at the numbers tells a pretty decisive tale.

- -

Percentage of revenue derived from iPhone sales:

- -
    -
  • 2012: 46.38%
  • -
  • 2013: 52.07%
  • -
  • 2014: 56.21%
  • -
  • 2015: 62.54%
  • -
-
- -

This is the part of Clark’s piece that got my attention. It’s a common refrain these days — just search Google for “Apple is too dependent on the iPhone”.

- -

Clark makes it sound like this is because the rest of Apple’s business is in decline, whereas the truth is that the iPhone continues to grow at an astonishing rate that even Apple’s other successful products can’t match. Is it worrisome that iPad sales continue to decline? Sure. Would it be better for Apple if the iPad were selling in iPhone-esque quantities? Of course. But iPad still sold 9.9 million units and generated $4.3 billion in revenue last quarter.

- -

Arguing that Apple is in trouble because the iPhone is so popular is like arguing that the ’90s-era Chicago Bulls were in trouble because Michael Jordan was so good. It’s true Jordan couldn’t play forever — and the iPhone won’t be the most profitable product in the world forever. But in the meantime, the Bulls were well-nigh unbeatable, and Apple, for now at least, is unfathomably profitable.1 Just like how it’s better to have loved and lost than never to have loved at all, it’s better to have tremendous success for some period of time than never to have had tremendous success in the first place. Right?

- -

What I don’t get is why Apple gets singled out for its singular success, but other companies don’t. 92 percent of Google’s revenue last year came from online advertising. And more importantly, I don’t get why Apple’s non-iPhone businesses are so quickly written off only because they’re so much smaller than the iPhone.

- -

Apple’s total revenue for last quarter was $51.5 billion. The iPhone accounted for $32.2 billion of that, which means Apple’s non-iPhone business generated about $19.3 billion in revenue. All of Microsoft in the same three months: around $21 billion. All of Google: $18.78 billion. Facebook: $4.5 billion. Take away every single iPhone sold — all of them — and Apple’s remaining business for the quarter was almost as big as Microsoft’s, bigger than Google’s, and more than four times the size of Facebook’s. And this is for the July-September quarter, not the October-December holiday quarter in which Apple is strongest.

- -

Nothing in the world compares to Apple’s iPhone business, including anything else Apple makes. But a multi-billion-per-quarter business here (Mac), a multi-billion-per-quarter business there (iPad), a “Services” division that generates more revenue than Facebook, and an “Other” category (Watch, Apple TV, Beats, iPod) that booked $3 billion in a non-holiday quarter — and it’s clear that Apple’s non-iPhone businesses, combined, amount to a massive enterprise.

- -

Here’s a Larry Dignon column about whether iPad Pro will make “iPad material to Apple again”:

- -
-

Apple’s iPad sales are on the borderline of being immaterial to -the company, but some analysts are betting that enterprise sales -of the iPad Pro can turn the product line around. […]

- -

Nevertheless, the iPad franchise is sucking wind relative to the -iPhone. Apple’s annual report shows the iPad is 10 percent of -overall sales. Once a business falls below 10 percent a company -doesn’t have to break it out. In other words, the iPad could be -lumped into “other” with the Apple Watch and iPod if current -trends continue.

-
- -

This is a product line that, in and of itself, generated just about exactly the same revenue last quarter as all of Google’s non-advertising business did for the entire fiscal year. But Apple is the company that is considered lopsided and worrisomely dependent upon a single product.

- -

Name a product introduced in the last five years that has been more successful than the iPad — either in terms of revenue and profit for its maker, or in terms of aggregate hours of daily use and customer satisfaction of its users. I can’t think of one.

- -

Now consider the Apple Watch. Fast Company called it “a flop” back in July. Here’s a guy on Quora — Jason Lancaster, editor of a website called Accurate Auto Advice — answering, in the affirmative, whether Apple has “already lost the market for self driving cars” (not joking):

- -
-

Third, Apple may have peaked. Call me a hater, but what reason is -there to assume Apple’s reputation is going to stay where it is? -The watch was a flop, and their only consistent source of success -is the iPhone, as the market for Macs and iPads is drying up (as -it is for all computer hardware companies).

-
- -

Forget the fact that Mac sales are growing, or that iPad sales, though in decline, remain roughly 10 million per quarter. What I enjoy about this is Lancaster’s having written off the Watch as a flop — he even uses the past tense.

- -

Here’s what that flop looks like:

- -
-

Apple has shipped seven million Apple Watches since its -introduction this spring, giving the technology giant a firm lead -in the nascent smartwatch market, according to researcher Canalys.

- -

That number falls shy of some Wall Street analysts’ expectations -for Apple’s first new device category since 2010. But, for -perspective, consider this: Apple sold more smartwatches from -April through September than all other vendors combined sold over -the past five quarters, Canalys reports.

-
- -

If we estimate the average selling price for an Apple Watch at $500 (reasonable), that’s $3.5 billion in revenue for the year to date — prior to the holiday quarter that is almost certainly going to be the strongest for watch sales annually.

- -
- -

Back to Bryan Clark’s TheNextWeb piece:

- -
-

Steve Jobs is almost entirely responsible for Apple’s cult-like -following.

- -

By streamlining the company in an attempt to make it profitable, -the same vision started to makes its way through every product -Apple created. Rather than bloated and flashy, Jobs created a -movement of decidedly minimalist devices that required not much -more than an occasional charge and a user that knew where the -power button was.

- -

Between aesthetically pleasing design, rock-solid hardware, and -software that responded as if it were built for the machine — -not in spite of it — Apple culture became a cult of -Jobs-worshipping consumers willing to buy anything with a -lowercase “i” in front of it.

-
- -

That never happened. The G4 Cube didn’t sell. iPod Hi-Fi didn’t sell. Those weren’t just non-hit products — they were both products that Steve Jobs himself really liked. I’ve heard that he had a stack of unopened iPod Hi-Fis in his office. Apple products have never been blindly accepted by the mass market — they’ve succeeded on their merits and by meeting actual demand. As I wrote two years ago:

- -
-

To posit that Apple customers are somehow different, that when -they feel screwed by Apple their response is to go back for more, -is “Cult of Mac” logic — the supposition that most Apple -customers are irrational zealots or trend followers who just -mindlessly buy anything with an Apple logo on it. The truth is the -opposite: Apple’s business is making customers happy, and keeping -them happy. They make products for discriminating people who have -higher standards and less tolerance for design flaws or -problems.

-
- -

Clark finally tells us what Apple’s biggest problems are:

- -
-

There are larger issues on the horizon: For example, how does -Apple compete with Windows and Android?

- -

Both have proven to be amazingly adept in recent years not only at -competing with Apple in form factor, but functionality as well.

- -

Two companies that are innovating, not searching for identity -outside of a singular product.

- -

Two companies that are on the way up, not down.

-
- -

Windows and Android, got it.

- -
-

The Apple Watch is great, but it’s never going to carry Apple like -the iPhone until it works like one. The watch is undeniably cool, -but it really fails to do anything better than your phone.

- -

To make matters worse, you have to have an iPhone close by in -order to even use most of its features. Similar Android models are -self-contained and only require an occasional sync.

- -

The autonomous car project sounds promising, but competing against -Google and Tesla in addition to auto industry giants like Lexus -and Mercedes is an uphill battle full of technology challenges, -government red tape and changing century-old transportation -conventions.

-
- -

The best I can gather from this mishmash of a conclusion is that Apple Watch should have somehow debuted as a first-generation product that could stand toe-to-toe with the iPhone (which is now in its ninth generation), and that Apple’s car product should already be here. If there were no rumors of an Apple car, we’d be hearing that Apple is going to miss out on the next big industry that is ripe for disruption from the tech industry. But because there are rumors and hints pointing to an Apple car, we’re hearing that cars are too difficult, the established companies too entrenched. Ed Colligan’s line for the ages — “PC guys are not going to just figure this out. They’re not going to just walk in.” — was also about an industry full of longstanding giants, Google, technology challenges, government red tape, and century-old conventions. Minus the “government red tape”, that’s a pretty good description of the watch and home entertainment system industries, too.

- -

I’m not here to argue the opposite of Colligan — that Apple’s success in these new fields is preordained — because that would be foolish. But it’s just as foolish to argue that Apple can’t succeed — or that anything less than iPhone-sized success in a new endeavor is a failure.

- -
-
-
    -
  1. -

    The iPhone, however, is unlikely to take a year off in the prime of its career to play baseball↩︎

    -
  2. -
-
- - - - ]]>
- ★ What Goes Up
- - - - tag:daringfireball.net,2015://1.31778 - 2015-11-11T13:08:58Z - 2015-11-13T08:05:24Z - - John Gruber - http://daringfireball.net/ - -The future of mass market portable computing involves neither a mouse pointer nor an x86 processor.

-]]>
- First impressions last a lifetime, goes the adage. You’re going to have to forget your first impressions of the iPad to understand the iPad Pro.

- -

When Apple introduced the original iPad in 2010, it was explicitly positioned in a new role for a device — somewhere between an iPhone and a MacBook. That seems obvious, but the problem, for the iPad, is that people loved their iPhones and MacBooks. The only way iPad would succeed, Steve Jobs said, was if it were “far better at doing some key things” than either an iPhone or MacBook.

- -

Apple succeeded. Simply by nature of having a bigger display, the iPad was better than the iPhone for numerous tasks — watching videos or reading long-form text, to name just two. No one would dispute that bigger displays are better for certain tasks — you can prove the productivity gains.

- -

What made the iPad better than a MacBook, in at least some ways, was more subjective than objective. Objectively, a MacBook was faster, by a large factor, could multitask, and offered a rich library of serious productivity apps. A Mac was, simply put, more powerful than an iPad — both in terms of hardware and software. The iPad had some objective advantages — battery life and the pixel density of its display are two that come to mind.1

- -

The trade-offs were obvious. The iPad offered the same conceptual simplicity and intimacy as the iPhone, with the “lean-back” ergonomics of a tablet, at the cost of power — hardware performance and software complexity.

- -

It was, in short, just a big iPhone. To the eyes of many in the tech industry, “just a big iPhone” was damning. They wanted the iPad to impress in terms of power. To the eyes of tens of millions of users, however, “just a big iPhone” was strong praise. An iPhone with a 10-inch display sounded just great.

- -

The intervening five years have turned all of this upside down. The iPad Pro now impresses solely by dint of its engineering. Anyone who doesn’t see this is blinded by their established impressions of the first few iPads.

- -

For the moment, put aside the form factor differences (tablet with optional keyboard vs. hinged clamshell), conceptual differences in iOS and OS X (direct touchscreen manipulation of full-screen apps vs. a mouse pointer and tiled windows) and software differences (simpler iOS apps vs. more complex OS X apps). All those points are worth consideration, but for now, put them aside. Right now, today, the iPad Pro is a peer to the current lineup of MacBooks in terms of computational hardware performance.

- -

The iPad Pro is without question faster than the new one-port MacBook or the latest MacBook Airs. I’ve looked at several of my favorite benchmarks — Geekbench 3, Mozilla’s Kraken, and Google’s Octane 2 — and the iPad Pro is a race car. It’s only a hair slower than my year-old 13-inch MacBook Pro in single-core measurements. Graphics-wise, testing with GFXBench, it blows my MacBook Pro away. A one-year-old maxed-out MacBook Pro, rivaled by an iPad in performance benchmarks. Just think about that. According to Geekbench’s online results, the iPad Pro is faster in single-core testing than Microsoft’s new Surface Pro 4 with a Core-i5 processor. The Core-i7 version of the Surface Pro 4 isn’t shipping until December — that model will almost certainly test faster than the iPad Pro. But that’s a $1599 machine with an Intel x86 CPU. The iPad Pro starts at $799 and runs an ARM CPU — Apple’s A9X. There is no more trade-off. You don’t have to choose between the performance of x86 and the battery life of ARM.

- -

We’ve now reached an inflection point. The new MacBook is slower, gets worse battery life, and even its cheapest configuration costs $200 more than the top-of-the-line iPad Pro. The iPad Pro is more powerful, cheaper, has a better display, and gets better battery life. It’s not a clear cut-and-dry win — MacBooks still have more RAM (the iPad Pro, in all configurations, has 4 GB of RAM, although Apple still isn’t publishing this information — MacBook Pros have either 8 or 16 GB), are expandable, and offer far more storage. But at a fundamental level — CPU speed, GPU speed, quality of the display, quality of the sound output, and overall responsiveness of interface — the iPad Pro is a better computer than a MacBook or MacBook Air, and a worthy rival to the far more expensive MacBook Pros.

- -

The entire x86 computer architecture is living on borrowed time. It’s a dead platform walking. The future belongs to ARM, and Apple’s A-series SoC’s are leading the way.

- -

The A9X didn’t come out of nowhere. Watching Apple’s A-series chips gain on x86 over the past five years, we’ve all been speculating about whether Apple might someday start using ARM chips in MacBooks. As of now, it’s only a question of whether they want to.

- -

What Apple Means by ‘Pro’

- -

With the Mac Pro, the “pro” really does stand for “professional”. There’s pretty much no reason for anyone to buy a Mac Pro unless their work is computationally expensive. There aren’t many people left whose work is slowed down regularly by the performance of their computer. The Mac Pro is aimed at that market. (That said, a higher-end iMac will outperform a Mac Pro in many tasks that aren’t well-suited to multicore parallel computing. The Mac Pro is due for an update.)

- -

With the MacBook Pro, on the other hand, “pro” isn’t really short for “professional”. It’s more like “deluxe” — a signifier that it’s a higher-end product than its non-pro siblings. Faster, better, and accordingly higher-priced. A MacBook Pro with 1 TB of SSD storage is indeed a terrific portable computer for “professional” use by, say, a photographer or film editor or software developer — people who truly stretch the performance of any computer today, portable or otherwise. But a decked-out MacBook Pro is also a terrific and perfectly reasonable choice for anyone who can simply afford one. MacBook Airs don’t have retina displays (and likely will never be upgraded to offer them), and the one-port MacBook is relatively slow.

- -

The iPad Pro is “pro” in the way MacBook Pros are. Genuine professionals with a professional need — visual artists in particular — are going to line up for them. But it’s also a perfectly reasonable choice for casual iPad users who just want a bigger display, louder (and now stereo) speakers, and faster performance.

- -

Anyone tying themselves in knots looking for a specific target audience for the iPad Pro is going about it the wrong way. There is no single target audience. Is the iPad Pro meant for office workers in the enterprise? Professional artists creating content? Casual users playing games, watching movies, and reading? The answer is simply “Yes”.

- -

Smart Keyboard and Converting to a Laptop Form Factor

- -

So unlike the original iPad of 2010, which carved out new territory between that of an iPhone and MacBook, the iPad Pro is clearly an alternative to a MacBook. I’m sure someone out there will carry both a MacBook (of any sort) and an iPad Pro while traveling, but I don’t really see the sense of that. The iPad Mini makes perfect sense as a travel companion to a MacBook. The iPad Air does too — especially for someone who watches a lot of video or prefers larger type while reading. But the iPad Pro stands as an alternative to a MacBook. If you want to carry a MacBook, you want a smaller, lighter iPad as a companion, and you don’t need a keyboard for it. If you want to carry an iPad Pro, you might as well get the Smart Keyboard cover and leave the MacBook at home.

- -

The trade-offs are varied. If you don’t type much, or don’t mind using the on-screen keyboard when you do, you’re probably already sold on the iPad-as-primary-portable-computer lifestyle. If you do type a lot and want a hardware keyboard, the appeal of the iPad Pro is going to largely hinge on your affinity for the Smart Keyboard.

- -

I’ve been using this iPad Pro review unit (128 GB, with cellular — top of the line kit, natch) for eight days, and most of that time I’ve had the Smart Keyboard attached. For just plain typing, it’s not that bad — I’ve written this entire review using it, Federico Viticci-style. I went into it thinking that my biggest complaint would be the keys themselves — I like my keyboards clicky, with a lot of travel. But I adjusted to it pretty quickly, and I kind of like the way it feels, as a tactile surface. It almost feels like canvas.

- -

My complaints and frustrations are more from the software, both iOS 9.1 itself and individual apps, both from Apple and third-party developers. Trying to use the iPad Pro as a laptop with the Smart Keyboard exposes the seams of an OS that was clearly designed for touchscreen use first. These seams aren’t new — I’m sure anyone who has tried using an iPad of any sort with a paired Bluetooth keyboard has run into the same things. This is simply the first time I’ve tried using an iPad with a hardware keyboard for an extended period for large amounts of work.

- -

I almost wrote “for large amounts of writing” in the preceding paragraph, but the problems with an iPad and a hardware keyboard are more than about typing. A large part of my work is reading, and with a laptop, the keyboard is a big part of the reading experience. In fact, with the iPad Pro, the keyboard is even more important than it is on a MacBook — and today, it falls short.

- -

Here’s what I mean. First, when the iPad Pro is open with the keyboard attached, holding your arm up to touch the screen for anything longer than a moment or two is ergonomically uncomfortable. Apple has stated for years that this is why they don’t make the displays on MacBooks or iMacs touchscreens (that, combined with the relatively tiny click targets of Mac OS X, which are designed for very precise mice and trackpads, not imprecise finger tips). Scrolling through a long document using the iPad Pro touch screen is uncomfortable when it’s in laptop position. Going through a slew of new emails, likewise. In laptop mode, I want to use the keyboard for these things — and in most cases, because of bugs and/or software limitations, I can’t. That the keyboard falls short in these cases is even worse on iPad than it would be on a MacBook, because a MacBook has a trackpad. The point is, if my fingers are on the keyboard, I don’t want to move my hands. With a trackpad, I don’t have to. With the iPad Pro, I do.

- -

It’s an ancient (meaning dating back to the Classic era) Mac convention that in a read-only scrolling view, you can use the space bar to page down. When your eyes get to the bottom of the display, you can just hit space and the view should scroll to show the next screen full of content — with the last line or two of the previous screen now repeated at the top of the new screen to provide context as your eyes move from the bottom to the top of the display. This works almost everywhere on OS X, and anywhere it doesn’t work should be considered a bug.

- -

On iOS 9.1, Safari tries to support this, but it is dreadfully buggy. Instead of paging down just less than one screen-height of content, it pages down about 1.5 screen-heights of content. It literally scrolls right past huge amounts of content, rendering the feature completely unusable.

- -

Here’s a sample page I’ve created to illustrate. It’s just a simple text file with 1,000 lines, numbered in order. When I view that on my MacBook Pro, I see lines 1–45 (and half of line 46). When I hit space to page, the view scrolls and I now see lines 44–89. Hit space again and the view scrolls to show lines 88–132.

- -

On iPad Pro, I see lines 1–49 initially. But when I hit space to page down, the view scrolls to show me lines 75–123. Lines 50–74 are completely skipped past. It’s not even just a line or two — it’s huge chunks of text. This happens in all web pages in Safari on iOS 9.1, and it is not specific to the iPad Pro and Smart Keyboard. I see the exact same behavior on any iPad with a paired Bluetooth keyboard.

- -

Mail is another app in which, on my Macs, I depend heavily on the keyboard for scrolling and selection. On iPad, Mail does let you move from message to message using the keyboard (⌘↓ and ⌘↑), but it doesn’t support scrolling the actual message content — the space bar does nothing, and the Smart Keyboard doesn’t have a proper Page Down key.

- -

The space bar doesn’t work as a Play/Pause toggle for audio or video, either. I think it should.

- -

I don’t think it’s inherently problematic that iOS has no conceptual support for a mouse pointer, and thus can’t work with any sort of trackpad. But, given this constraint, good support for navigating as much of the UI as possible using the keyboard is more important on the iPad than it is on the Mac. But iOS’s support for navigating using the keyboard is worse.

- -

Another problem: when editing a long document, if you use the arrow keys to move the insertion point above the first line on screen or below the last line on screen, the insertion point just disappears off screen. The view doesn’t scroll to keep the insertion point visible, which is clearly what should happen (and does happen on OS X). Surely iOS will work this way eventually, but right now it still shows its roots as a touchscreen OS where a hardware keyboard is a decided second-class citizen.

- -

All is not lost, however. ⌘-Tab works for app switching just like it does on the Mac. Tap it and release and you switch to the most-recently used app. Tap it and keep holding down ⌘ and you get a visual switcher showing the 10 most-recently-used apps. (Again, this works with any hardware keyboard connected to any iPad — it’s just that this has been the first time it’s been relevant to me, personally.) The Smart Keyboard lacks a Home button, but there is a system-wide shortcut that maps ⌘-Shift-H to “Home”. Not bad, but once you’re at the iOS home screen, there’s not much you can do without touching the screen. For a few days, I sort of wished that I could use the arrow keys to navigate the home screen, with the selected app icon popping “up” like in the “focus” UI of the new Apple TV. But that idea, I suspect, is too far afield from the regular touchscreen-based UI of the iOS home screen. My keyboard idea requires a select-then-act two-stage model — the regular touch-based launcher is single-stage: just tap.

- -

But then I realized that the problem I wanted to solve wasn’t that I wanted the home screen to be keyboard-navigable. The problem was that I wanted to use the keyboard to launch apps that weren’t listed in the ⌘-Tab switcher. To do that on iOS without a hardware keyboard, you go home, then tap the app. With a keyboard, though, you can do it, just in a different way.

- -

Hit ⌘-Space system wide, and you’ll be taken to the home screen’s system-wide “Quick Search”. It’s like the iOS equivalent of Spotlight. Start typing the name of the app you want to launch, and there it is.

- -

But go ahead and play a sad trombone wah-wah here, because at this point, you still have to pick your arm up and touch the screen to launch the app. You can also use Quick Search for starting a web search in Safari, or anything else. But you can’t use the keyboard arrow keys to navigate the list of results. (Another problem with Quick Search using the keyboard: you have to wait a second or so for the Quick Search text field to accept input. I’m pretty sure it’s because we’re waiting for the animation to complete — first to show the home screen, then to jump to Quick Search. So if you type ⌘-Space and immediately begin typing what you’re looking for, the first few characters you type are lost. The user should never have to wait for the computer, especially if it’s just for an animation. Any Mac user with muscle memory trained by LaunchBar, Alfred, Quicksilver, or even Spotlight is going to find this enforced delay on iOS maddening.)

- -

This lack of keyboard support is prevalent system-wide. In Messages, if you start a new conversation and type the partial name of a contact, you can’t select from the list of matches using arrow keys or auto-complete the name you’ve partially typed using Tab. You’ve got to — you guessed it — reach up and touch the screen. You can use the arrow keys to select from a list of suggestions in the recipients fields in Mail, however, and arrow keys also work for selecting from the list of suggestions in the Safari location field.

- -

The bottom line is that the potential of the iPad Pro as a laptop is tremendous. The keyboard is just fine for typing, and the magnetic connection between the iPad Pro and the keyboard is surprisingly sturdy. You can absolutely use it as a literal laptop without any worry that the iPad Pro is going to fall off the Smart Keyboard. I even like the 4:3 aspect ratio — it shows more lines of text when reading than my 13-inch MacBook Pro. It also occupies a smaller footprint than an open MacBook Pro, meaning it should fit better on the seatback tray of an airplane. But the lack of pervasive support for keyboard-based UI navigation in iOS is a problem for anyone with longstanding Mac keyboard shortcuts ingrained in their muscle memory.

- -

As an actual cover, the Smart Keyboard does feel thick, and when closed, it bothers me a little that it’s thicker on the outer two thirds (where the keyboard is folded under) than the inner third. I wouldn’t recommend the Smart Keyboard for anyone who doesn’t plan to actually use the keyboard quite a bit. But if you do plan on using the keyboard frequently, the trade-off in thickness (compared to the non-keyboard Smart Cover) is well worth it.

- -

(It occurs to me that for many people, the Smart Keyboard might best be thought of not as a thick cover, but as a thin very portable desktop docking station.)

- -

Keyboard Bugs

- -

I experienced some flakiness with the keyboard throughout the week. Sometimes, system-wide keyboard shortcuts would stop working: ⌘-Tab, ⌘-Space, and ⌘-Shift-H. Typing within apps still worked, and keyboard shortcuts within any given app still worked, but the system-wide shortcuts inexplicably stopped working.

- -

Less frequently, I’ve seen the opposite problem: the system-wide keyboard shortcuts work, but keyboard shortcuts within any given app stop working. (iOS 9 has a very clever feature, by the way: press and hold the ⌘ key and you’ll see a HUD pop-up displaying all keyboard shortcuts available in the current context. This makes keyboard shortcuts more discoverable than they are on the Mac, where they’re spread across multiple menus in the menu bar.)

- -

In either case, I’ve been able to fix these keyboard problems by detaching and re-attaching the iPad from the Smart Keyboard. I don’t know if it’s a bug in iOS 9.1 or a faulty Smart Keyboard. (Apple has shipped me a second Smart Keyboard to test, but it won’t arrive until later in the day, after this review has been published. I’ll update it after the replacement arrives.)

- -

Apple Pencil

- -

It’s about precision: accuracy where you touch (Apple claims sub-pixel precision on screen), accuracy regarding pressure, and low latency regarding what you see on screen. I am not an illustrator, but I do know my own signature. My signature never looks like my actual signature when I have to sign electronically on a point-of-sale terminal. Usually it doesn’t even look close. On iPad Pro with Apple Pencil, it looks exactly like my signature when I sign with paper and ink. My handwriting looks like my handwriting, period (for better or for worse).

- -

All previous iOS devices have touchscreens designed for input from one source: fingertips. Fingertips are relatively fat and capacitive. The relatively fat size and imprecise location of a finger on screen is why tap targets are relatively larger and more spaced apart on iOS than OS X. This is also why third-party styluses for iOS devices have broad tips made of capacitive rubber — they’re more or less fake fingertips. The capacitive touchscreens on iPhones and (non-Pro) iPads aren’t designed for “fine tips”.

- -

Apple has done a few things regarding sampling the screen for input with Apple Pencil. First, there is something new in the display itself — something in the layer between the glass surface and the LCD display, I think. Or perhaps it’s under the LCD? Apple alludes to it in the Jony Ive-narrated video on the Apple Pencil web page, but they’re not really talking about it in detail.

- -

For capacitive (finger) touch, the iPad Pro samples at twice the rate of previous iPads — 120 times per second instead of 60. With the Pencil, though, the iPad Pro samples 240 times per second. The way the Pencil works requires cooperation with the display, and so there’s no way this Pencil could be made to work with existing iPads. The Pencil is not iPad Pro-exclusive out of product marketing spite — it’s exclusive to the Pro because the two were engineered in coordination with each other. And if Apple had designed the Pencil differently, to allow it to work with existing iPads, there’s no way it could have had this level of accuracy, because the tip would have needed to be broader and capacitive. (The Pencil’s tip is not capacitive at all — it doesn’t register as a touch at all on any other iOS device.)

- -

My guess is we’ll start to see Pencil support in future iOS devices in addition to the iPad Pro, starting with the iPad Air 3.

- -

Because the Pencil is round-barreled and has no clip on the cap, I was worried that it would roll around (and eventually, off) a table top. But it’s actually weighted inside, sort of like a Weeble Wobble, so unless it’s on a sloped surface, it won’t roll more than an inch or so before settling in place. In hand, I can’t tell that it’s weighted like this.

- -

I think most people who buy an iPad Pro are going to want a Smart Keyboard. The Apple Pencil is the more technically remarkable peripheral, but I suspect it’ll prove useful to far fewer people. Sketching apps like 53’s Paper and Apple’s own built-in Notes app certainly have appeal and utility to people who aren’t artists, but I suspect a lot of Apple Pencils are going to be bought out of curiosity and then go largely unused.

- -

For actual illustrators and artists, however, the Pencil and iPad Pro seem poised to be a career/industry-changing combination. What has been largely abstract — drawing using a device over here, looking at the results on a screen over there — can now be direct.

- -

Miscellaneous

- -
    -
  • Weight: The iPad Pro certainly feels heavier than recent iPads, but only in a way that’s commensurate with its increased size. It’s not too heavy.

  • -
  • Audio: The speakers are surprisingly loud. Apple told me the iPad Pro produces three times the audio volume of the iPad Air, and that certainly matches my experience. If you use your iPad as a small TV, the audio improvements might be more meaningful than the bigger display. The four-speaker stereo system is also very clever — no matter which way you rotate the iPad Pro, the top two speakers are for treble and the bottom two for bass.

  • -
  • Snap: Speaking of audio, if there’s a downside to the snug connection between the iPad Pro and the Smart Keyboard, it’s that the magnetic connection makes a rather loud snap when you connect or disconnect it. I can imagine some scenarios — in bed with a sleeping spouse, say — where this might be a problem.

  • -
  • Size classes: I think even Apple’s own apps are still figuring out how best to arrange layouts on this larger display. For example, in Mail, when the iPad Pro is in portrait, it only shows one column at a time. I think there’s clearly enough room horizontally, even in portrait, for a two-pane layout (narrow list of messages on left, wide message detail on right). The iPad Pro in portrait is as wide as the iPad Air in landscape — and the iPad Air in landscape uses two panes for Mail. Third-party developers are going to want to adjust their apps after they get a feel for what it’s like to use the iPad Pro for real.

  • -
  • Battery life: Simply outstanding. I didn’t even plug it in once between Monday and Friday, and it still had plenty of charge left. I’ve been using it for eight continuous hours as I type this sentence, and it still has more than a 50 percent charge remaining.

  • -
  • Missing apps: It’s been like this ever since the original iPad, but it still strikes me as odd that the iPad version of iOS lacks the Calculator, Weather, and Stocks apps. The Mac doesn’t have “apps” for Weather or Stocks, but it does have widgets for them in Notification Center. And it seems downright crazy for a computer not to have a built-in means for doing arithmetic. (Although you can do some arithmetic using Quick Search.)

  • -
  • Touch, Don’t Touch: For the past week I’ve really only used two computers. The iMac on my desk, and this iPad Pro. Today, though, I used my MacBook Pro while the iPad Pro was running benchmarks. And within a few minutes, I did something I have never once done before: I reached up and tried to touch something on the display. Ten minutes later I did it again. I point this out not to argue that I think MacBooks should have touch screens, but simply as an observation that even a lifelong Mac user can quickly get accustomed to the iPad Pro as a laptop.

  • -
- -

Conclusion

- -

From a hardware perspective, the iPad Pro strikes me as a seminal device. It runs faster than the Intel x86-based MacBooks, gets better battery life, and costs significantly less. And it has a better display with significantly more pixels than even a 15-inch MacBook Pro.

- -

Software-wise, support for the Smart Keyboard needs to get even smarter — but I’d be shocked if it doesn’t. For me, the iPad Pro marks the turning point where iPads are no longer merely lightweight (both physically and conceptually) alternatives to MacBooks for use in simple scenarios, to where MacBooks will now start being seen as heavyweight alternatives to iPads for complex scenarios.2

- -

Is it a MacBook replacement for me, personally? No. For you? Maybe. For many people? Yes.

- -

It brings me no joy to observe this, but the future of mass market portable computing involves neither a mouse pointer nor an x86 processor.

- -
-
-
    -
  1. -

    It’s kind of funny to think of a 2010 iPad with its 133 PPI display as “high resolution” — such a display looks comically fuzzy by today’s standards. But at the time it was a noticeably sharper display than what was in the MacBooks of the day — a 2009 13-inch MacBook Pro had a display with 113 PPI resolution↩︎

    -
  2. -
  3. -

    iOS 9’s split-screen multitasking really shines on the iPad Pro. I’ve found it useful on my iPad Air, but it’s downright natural on the iPad Pro. ↩︎︎

    -
  4. - -
-
- - - - ]]>
- ★ The iPad Pro
diff --git a/RSParser/Tests/RSParserTests/Resources/EMarley.rss b/RSParser/Tests/RSParserTests/Resources/EMarley.rss deleted file mode 100755 index 7c833eeef..000000000 --- a/RSParser/Tests/RSParserTests/Resources/EMarley.rss +++ /dev/null @@ -1,97 +0,0 @@ - - - <![CDATA[Stories by Liz Marley on Medium]]> - - https://medium.com/@emarley?source=rss-b4981c59ffa5------2 - - https://d262ilb51hltx0.cloudfront.net/fit/c/150/150/0*I9s5OlzJw_En0NzC.jpg - Stories by Liz Marley on Medium - https://medium.com/@emarley?source=rss-b4981c59ffa5------2 - - Medium - Sun, 28 Aug 2016 17:27:51 GMT - - - - - <![CDATA[UI Automation & screenshots]]> -

Here’s a partial collection of links from my talk today…

]]>
- https://medium.com/@emarley/ui-automation-screenshots-c44a41af38d1?source=rss-b4981c59ffa5------2 - https://medium.com/p/c44a41af38d1 - - Sat, 07 May 2016 23:53:30 GMT -
- - <![CDATA[They didn’t.]]> -

“The [software developer tool] team clearly doesn’t use [that tool] themselves.”

]]>
- https://medium.com/@emarley/they-didn-t-3a4dab489f45?source=rss-b4981c59ffa5------2 - https://medium.com/p/3a4dab489f45 - - Sat, 09 Jan 2016 15:29:25 GMT -
- - <![CDATA[Side quest: Drawing]]> -

]]>
- https://medium.com/@emarley/side-quest-drawing-b959ded1a1a4?source=rss-b4981c59ffa5------2 - https://medium.com/p/b959ded1a1a4 - - Wed, 09 Dec 2015 03:37:35 GMT -
- - <![CDATA[And if I somehow lose the iPad Pro, I can find that with Find My iPhone.]]> - ]]> - https://medium.com/@emarley/and-if-i-somehow-lose-the-ipad-pro-i-can-find-that-with-find-my-iphone-e9aa43486521?source=rss-b4981c59ffa5------2 - https://medium.com/p/e9aa43486521 - - Mon, 23 Nov 2015 19:38:20 GMT - - - <![CDATA[Though not as much more weight as you might expect.]]> - ]]> - https://medium.com/@emarley/though-not-as-much-more-weight-as-you-might-expect-7b33fe989f6e?source=rss-b4981c59ffa5------2 - https://medium.com/p/7b33fe989f6e - - Mon, 23 Nov 2015 19:37:38 GMT - - - <![CDATA[I avoided art classes in high school and college because I was afraid they would hurt my GPA.]]> - ]]> - https://medium.com/@emarley/i-avoided-art-classes-in-high-school-and-college-because-i-was-afraid-they-would-hurt-my-gpa-ab916601f2ad?source=rss-b4981c59ffa5------2 - https://medium.com/p/ab916601f2ad - - Mon, 23 Nov 2015 19:37:13 GMT - - - <![CDATA[Finding Value]]> -

I lose things a lot. Sometimes they’re just misplaced, sometimes gone forever. I don’t know if I have ever run out of ink in a pen—there’s…

]]>
- https://medium.com/@emarley/finding-value-20a90bf5ebf?source=rss-b4981c59ffa5------2 - https://medium.com/p/20a90bf5ebf - - Mon, 23 Nov 2015 19:34:18 GMT -
- - <![CDATA[Replaying this post in my head last night, I regret this word.]]> -

Keyboard shortcuts, and other little details may be programmatically simple to set up, but they are still an important part of an app’s…

]]>
- https://medium.com/@emarley/replaying-this-post-in-my-head-last-night-i-regret-this-word-d8ed0b43f0f9?source=rss-b4981c59ffa5------2 - https://medium.com/p/d8ed0b43f0f9 - - Tue, 10 Nov 2015 18:08:19 GMT -
- - <![CDATA[Betterment]]> -

I moved from Senior Test Pilot to Software Engineer last month.

]]>
- https://medium.com/@emarley/betterment-e0ef45fcd284?source=rss-b4981c59ffa5------2 - https://medium.com/p/e0ef45fcd284 - - Tue, 10 Nov 2015 02:17:46 GMT -
- - <![CDATA[This is a test.]]> -

This is only a test.

]]>
- https://medium.com/@emarley/this-is-a-test-6ab141a1c5b5?source=rss-b4981c59ffa5------2 - https://medium.com/p/6ab141a1c5b5 - - Sun, 20 Sep 2015 07:00:44 GMT -
-
-
\ No newline at end of file diff --git a/RSParser/Tests/RSParserTests/Resources/KatieFloyd.rss b/RSParser/Tests/RSParserTests/Resources/KatieFloyd.rss deleted file mode 100755 index c8b9d501e..000000000 --- a/RSParser/Tests/RSParserTests/Resources/KatieFloyd.rss +++ /dev/null @@ -1,352 +0,0 @@ - - - - - - - en - katiefloyd - - - - http://www.katiefloyd.com - <![CDATA[Katie Floyd]]> - https://static.feedpress.it/logo/katiefloyd.png - - Katie Floyd - http://www.katiefloyd.com - Tue, 23 Aug 2016 21:32:07 +0000 - en-US - Site-Server v6.0.0-8715-8715 (http://www.squarespace.com) - - - Special Mac Power Users for Relay FM Members - Katie Floyd - Thu, 25 Aug 2016 22:20:00 +0000 - http://tracking.feedpress.it/link/980/4243452 - 50c628b3e4b07b56461546c5:50c658a6e4b0cc9aa9ce4405:57bcbe83e4fcb567fdffc020 - - - - - - - - - - -

Relay FM has created a membership system to allow listeners to support the hosts of their favorite Relay FM shows. Listeners can choose to support a specific show like Mac Power Users, or support all the shows on the network. One of the perks of membership is that members will receive a bonus episode shows during Relay's anniversary week in August, including a very special bonus episode of Mac Power users.

Since this show didn't have to conform to a typical format, David and I knew instantly what we wanted to talk about. Star Trek and Star Wars. We spent almost an hour talking about our love for the series, how we became fans, our favorite characters and movies, what we think about the reboots and some of our traditions. It was a lot of fun.

If you're a Relay FM member, you can grab your special bonus episode of Mac Power Users now, along with special episodes of many of the other shows on Relay. If you're not a member, you can sign up now and gain access to all the members-only content.

My sincere thanks to all the members for your kind support of David and me and all the wonderful hosts on Relay FM!

]]>
-
- - Mac Power Users #336: iPhone Productivity - Relay FM - Katie Floyd - Tue, 23 Aug 2016 21:19:22 +0000 - http://tracking.feedpress.it/link/980/4224355 - 50c628b3e4b07b56461546c5:50c658a6e4b0cc9aa9ce4405:57bcbe39b8a79b49057ca264 - This week on Mac Power Users David and I share some of the most efficient productivity tasks and apps appropriate for the iPhone. Topics include calendars and contacts, email, task management, scanning, writing, and additional utilities for productivity.

This episode of Mac Power Users is sponsored by:

  • Casper: Because everyone deserves a great night sleep. Get $50 off with the code ‘MPU’
  • Eero: Blanket your home in fast, reliable WiFi.
  • The Omni Group We're passionate about productivity for Mac, iPhone and iPad. 
  • Automatic The connected car company that improves your driving and integrates your car into yoru digital life. Enter code MACPOWER to get 20% off your purchase.
]]>
-
- - Katie's Week In Review: August 21, 2016 - Katie Floyd - Sun, 21 Aug 2016 20:23:47 +0000 - http://tracking.feedpress.it/link/980/4204283 - 50c628b3e4b07b56461546c5:50c658a6e4b0cc9aa9ce4405:57ba0da8b8a79bd395ce38f9 - It’s a crazy weekend for me so I’m leaving you with a more abbreviated “link list” for the week in review post this week. Nevertheless, here are a few links of note for the week ending August 21, 2016:

  • Apple Announces Environmental Progress in China & Applauds Supplier Commitment to Clean Energy - Apple continuing their commitment to the environment, this time by putting pressure on their suppliers in China to commit to clean energy. Apple suppler Lens Technology has agreed to run its Apple operations on entirely renewable energy.
  • Tim Cook, the interview: Running Apple ‘is sort of a lonely job’ - I enjoyed this interview by Jena McGregor of The Washington Post discussing his first five years as CEO after Steve Jobs. Cook talks about some of the missteps as CEO, how the job can be lonely at times and living up to expectations.
  • Apple, Google, FCC Team up to Stop Spam Robocalls Jeff Gamet of The Mac Observer explains how some of the biggest players in the telecom industry are finally stepping up to address the problem with spam phone calls. I received one earlier today. It’s gotten to the point where I don’t answer my phone (and sometimes miss important calls) if I don’t recognize the number. There’s a new feature in iOS 10 that will allow Apple to query a database to attempt to determine if a call is likely spam, but so far Apple hasn’t gone so far as to build or maintain one itself.
  • New Ways to Control Your Experience on Twitter - Twitter has finally taken some positive steps to allow people to attempt to filter out trolls and limit the notifications they receive from. Twitter says this is just the start of their steps to take back control of the platform and control harassment. We’ll see. Right now it seems that these features are only available in the native Twitter apps, which I don’t use because they also come with tradeoffs. Hopefully Twitter will open these features up to third party developers.
  • Introducing Automatic Pro - Automatic rolled out a new version of their connected car adapter this week. The highlights include a new 3G connection included in the purchase price so it doesn’t have to always be connected to bluetooth. This is great because I find my iPhone’s bluetooth connection can be flakey sometimes causing my Automatic to drop connection. I’ll be picking up one of the new adapters. (Full disclosure, Automatic sponsors Mac Power Users - you can save $20 using this link.)
  • AT&T Introduces new MobileShare Plans - Following a similar move by Verizon, in advance of the new iPhone announcement AT&T has announced new mobile data plans. The plans are more expensive (of course) but in lieu of overage charges you’ll have the option to throttle your speeds down to 128kbps when your exceed your data cap. While painful, it might be a nice alternative if you only have a short time left before your billing cycle resets.
  • New Star Trek postage stamps are being released to celebrate the 50th anniversary. They’re available now for pre-order through the USPS.
]]>
-
- - Mac Power Users #335: Workflows with John Voorhees - Katie Floyd - Tue, 16 Aug 2016 15:03:11 +0000 - http://tracking.feedpress.it/link/980/4160390 - 50c628b3e4b07b56461546c5:50c658a6e4b0cc9aa9ce4405:57b32b40be659468fb6d1031 - This week on Mac Power Users, David and I chat with John Voorhees. John is an attorney by day but also develops software and writes for Mac Stories. We chat with John about how he juggles all his tech related tasks while managing a busy day job. (We sympathize.) 

This episode of Mac Power Users is sponsored by:

  • PDFpen from Smile With powerful PDF editing tools, available for Mac, iPad, and iPhone, PDFpen from Smile makes you a Mac Power User.
  • 1Password Have you ever forgotten a password? Now you don't have to worry about that anymore.
  • Fujitsu ScanSnap ScanSnap Helps You Live a More Productive, Efficient, Paperless Life. 
  • Squarespace: Enter offer code MPU at checkout to get 10% off your first purchase.
]]>
-
- - Katie's Week in Review: August 14, 2016 - Katie Floyd - Sun, 14 Aug 2016 21:11:27 +0000 - http://tracking.feedpress.it/link/980/4146486 - 50c628b3e4b07b56461546c5:50c658a6e4b0cc9aa9ce4405:57b0de6be58c62414be1f6ab - Mark Gurman is back with new rumors about the iPhone and MacBook Pros. That and other exciting links of note for the week ending August 14, 2016:

  • Mark Gurman, who is known for his accuracy recently in reporting Apple rumors, claims to have details of the new iPhones. Writing for his new publication, Bloomberg, Gurman claims that the standout feature will be a dual-camera system on the larger iPhone, a re-designed home button that appears to be touch sensitive rather than a physical click wheel and removal of the headphone jack. Gurman says we can also expect a hardware design similar to that of the past two years, perhaps making way for a more radical redesign next year for the 10-year anniversary of the iPhone. Gurman’s report, if true, seems to confirm previously reported rumors but doesn’t seem to bring a whole lot of additional information to the table.
  • Also this week at Bloomberg, Gurman claims Apple is planning its first major Pro laptop overhaul in four years. Again, Gurman’s report seems to give credit to previously reported rumors claiming the notebooks will be thinner, include a touch screen strip for function keys and feature more powerful processors along with USB-C technology along with a Touch ID sensor. However, Gurman says the MacBook Pros likely won’t debut alongside the new iPhone at an event rumored for September 7th. I’ve been hanging on to my 13" MacBook Air waiting for Apple to release new notebooks and if most of this is true, it sounds like just the update I’ve been waiting for.
  • This week Fast Company ran a cover story promoting an “inside look at Tim Cook’s Apple” It features an interview with Eddie Cue and Craig Federighi with follow-up by TimCook along with a sidebar featuring Bozoma Saint John about Apple Music.
  • It’s about time. Google has finally updated Google Docs, Sheets and Slides for iOS with split view support. Jason Snell of Six Colors has the details. Like Jason, David and I use Google Docs and Sheets to plan and prep Mac Power Users and the inability to use these apps in split screen on iOS was problematic. It only took them a year - but I guess better late than never.
  • Google has also announced that the Chrome browser will begin blocking Flash that runs in the background of webpages and make HTML5 the default beginning in September. John Voorhees of MacStories reports that Chrome 55 will make HTML5 the default except for sites which only support flash. I don’t load Flash on any of my computers but find myself occasionally using Chrome (which currently has Flash support built-in) to load a site that requires it. I’m unclear how that might change in the future, but it sounds like Chrome will still support Flash in the future, just not as a default.
  • IFTTT has released a new feature that allow recipes to live inside other apps. This will allow users to browse and activate select IFTTT recipes from within the developer’s app. Currently integrations are live with partners including LIFX, Garageio and Skybell with additional integrations coming online soon from partners including Automatic and Ring.
  • Last week I mentioned I was interested in the Logitech Create 9.7" iPad Pro keyboard case and I might have considered it if I hadn’t just bought the Apple Smart Keyboard for my iPad. This week Phil Dzikly of iLounge posted a review of the Crate 9.7". Overall it earned a high recommendation from the iLounge editors, though it appears to be a bit bulkier than Apple’s solution.
  • My thanks to MailButler for sponsoring the site this week. If you’re interested in having your product or service featured on KatieFloyd.com, consider an RSS sponsorship, details can be found here.
  • Finally, this week the official trailer for Rogue One: A Star Wars Story was released. I have to admit, it gave me goosebumps. The trailer runs about 2 minutes. I can’t wait for the 2 hour Incomparable episode about it.
]]>
-
- - Mac Power Users #334: MPU Live: "She Can't Take It Anymore, Captain!" - Katie Floyd - Tue, 09 Aug 2016 23:40:00 +0000 - http://tracking.feedpress.it/link/980/4107789 - 50c628b3e4b07b56461546c5:50c658a6e4b0cc9aa9ce4405:57a7d4e9e4fcb59aef039cba - This week's Mac Power Users was a live show. David and I are joined by our pal Victor Cajiao who discusses all the steps of producing a modern music album. We also take listener questions and help troubleshoot an accidentally reformatted hard drive, discuss password schemes, using a ScanSnap for photos, share listener feedback on our Special Event and Keynote shows, discuss options for Evernote and share listener tips.

This episode of Mac Power Users is sponsored by:

  • 1Password Have you ever forgotten a password? Now you don't have to worry about that anymore. 
  • Gazelle Sell your iPhone for cash at Gazelle! 
  • Marketcircle We help small business grow with great Mac, iPhone and iPad apps including Daylight and Billings Pro.
  • Sanebox Stop drowning in email!
]]>
-
- - Sponsor: Inbox Zero In No Time With MailButler For Apple Mail - Katie Floyd - Mon, 08 Aug 2016 12:00:00 +0000 - http://tracking.feedpress.it/link/980/4094116 - 50c628b3e4b07b56461546c5:50c658a6e4b0cc9aa9ce4405:57a75d06ebbd1acee61ea0b0 - - - - - - - - - - - - - - - -

Every email user is familiar with this problem: important incoming emails often get lost, if they are not responded right away. They drop down quickly to the bottom of your inbox, replaced by lots of new messages.

With its new Snooze feature, MailButler offers a solution for this.

If you want to deal with the email later, you just have to press the “Snooze” button, and set up the time, when you want to get back to it: be it in several hours, or at the beginning of the next workday.

After that the email is removed from your inbox and moves back to the top later, right at your preferred time. Snoozed emails are also available for you the entire time in a special folder. Zero Inbox has never been so easy to reach before!

MailButler is an add-on, which adds many great productivity boosting features to your Apple Mail. Among them is the ability to schedule, undo, and track emails, convert emails to notes, upload email attachments regardless of size, and more. The developers plan to add new things to this list.

You can learn more about the features and also download the program here.

]]>
-
- - Katie's Week In Review: August 7, 2016 - Katie Floyd - Sun, 07 Aug 2016 16:08:30 +0000 - http://tracking.feedpress.it/link/980/4087132 - 50c628b3e4b07b56461546c5:50c658a6e4b0cc9aa9ce4405:57a75ce8ebbd1acee61ea021 - We continue in the lull of summer with not a whole lot of exciting tech news to report. Nevertheless I did manage to find a few interesting links of note for the week ending August 7, 2016:

  • Apple released iOS 9.3.4 this week with a single security fix. Notably, this patch fixes the “Pangu jailbreak exploit” related to a memory corruption issue.
  • On the topic of security, Rene Ritchie of iMore reports that Apple has created a new security bounty program which launches in September offering cash rewards for reporting of security vulnerabilities. iMore has an FAQ on the program.
  • Apple released their long awaited Remote App for the fourth Generation Apple TV. Instead of an update to their existing App, this is an entirely new application available for iPhone. There are some pros and cons of the new application, Dan Moren, writing for Six Colors, has an overview.
  • Writing for Recode, Peter Kafka says that Apple is working on a new digital TV Guide type system to tell users what’s on Apple TV and how to watch it. According to Recode, the idea is to let users see what kind of programing is available through their various video apps and services without having to open the individual applications. Supposedly the “single sign-on” service previewed at WWDC earlier this year was the first step of the plan. I can’t help but think that TiVo does something similar with their “OnePass” feature that allows users to browse and record shows from a variety of different services.
  • The 2016 Olympics kicked off in Rio this week. Though I’m not a huge sports fan, I do like to watch the highlights. I’m primarily watching NBC’s primetime coverage using my OTA Antenna. However, watching the Olympics can be a problem for cord cutters, especially if you want to watch the games live, not delayed. Michael Ansaldo writing for TechHive has put together a guide for how to watch the Olympics without paying for cable TV.
  • Logitech just released a new Create Keyboard for the 9.7" iPad Pro. Oscar Raymundo at Macworld takes a first look. This cover features backlit keys, has a row of iOS specific shortcuts, and includes a protective folio case with holder for the Apple Pencil. I just bought the Apple SmartKeyboard and back case a couple of weeks ago. Although I’m happy with my setup, I might have opted to try the Logitech had I waited a few more weeks.
  • Federico Viticci at MacStories highlights a problem with Stolen iPhones and Identity Theft and shares the story of very convincing phishing attempt his mother received after having her iPhone stolen a few weeks prior.
  • One of my favorite Apps, 1Password has introduced a new subscription service for individuals. (Don’t worry, the standalone service isn’t going away for those of you that are happy with that service.) The new subscription service offers some additional features including built-in automatic sync, data loss protection, web access to all your data on 1Password.com and includes access to all the the most updated versions of all the 1Password applications. Pricing for an individual plan is $2.99 a month with a special launch offer offering 6-months free.
  • The OmniGroup also announced a major update to an App I use regularly, OmniFocus now supports end-to-end encryption. Thanks to recent updates to all the apps on their platform, data Synced to the OmniFocus servers are now securely encrypted when at rest on the server. One you’ve updated all your devices, you’ll receive a notification that your database is available for upgrade.
  • Finally this week, for years the original model of the U.S.S. enterprise has hung in the Smithsonian National Air and Space Museum. However, for the past several it’s been relegated to the gift shop as the model has deteriorated. The Enterprise was recently restored to its original glory and is now back on display in the Milestones of Flight Hall. Here’s a video showing the process of the restoration.
]]>
-
- - First Look: MOS Go USB-C Battery Pack - Katie Floyd - Sat, 06 Aug 2016 23:29:49 +0000 - http://tracking.feedpress.it/link/980/4081735 - 50c628b3e4b07b56461546c5:50c658a6e4b0cc9aa9ce4405:57a6720337c58153c17e089d - - - - - - - - - - -

Charging a laptop with a battery pack was something that has previously only been done with expensive adapters. With more laptops switching to USB-C for power, battery packs similar to the ones we carry to recharge our iOS devices can now be used to recharge computers. Of course, not all batteries are created equal. Most batteries designed for cell phones and tablets will not be able to keep up with the power demands required by laptops and may only provide a minimal charge to a USB-C notebook, and only then if the notebook is powered off.

The MOS Go is a battery with 12,000 mAh, 44.4 Wh capacity which the manufacturer claims will recharge a 2015 MacBook up to 75% capacity. More importantly, it will also provide power to a MacBook that is powered on, meaning you don’t have to shut down your computer to recharge the battery. I viewed a video demonstration provided by the manufacturer where a MacBook with the screen at 50% brightness was connected to the MOS Go and was able to gain about 30% charge over 30 minutes.

The MOS Go has a single USB-C input and thus requires a USB-C cable to charge, but has both standard USB and USB-C outputs. This means you can recharge both your USB-C laptop as well as an iPhone or iPad with a single battery pack. With a 12,000 mAh capacity, the MOS Go should charge most smartphones up to four times and most tablets twice.

I’ve been playing with a MOS Go for about a week now and it’s a nice battery. The build quality is solid and I like that it has dual USB and USB-C outputs. My only wish is it has both USB and USB-C inputs for recharging. If you have a USB-C capable device, like the new MacBook or one of Google’s Chromebooks, adding a USB-C power pack to your travel bag is something worth considering. You can order the MOS Go direct from the manufacturer for $119.95. It’s also available on Amazon.

Now, if only I could get Apple to release a new MacBook Pro with USB-C ports…

]]>
-
- - Time Warner Buys 10% Stake in Hulu, Now Don't Screw This Up - Katie Floyd - Wed, 03 Aug 2016 15:47:36 +0000 - http://tracking.feedpress.it/link/980/4052707 - 50c628b3e4b07b56461546c5:50c658a6e4b0cc9aa9ce4405:57a211806b8f5b9351bb7efd - - - - - - - - - - -

As reported by CNET, Time Warner has bought a 10% stake in Hulu and as part of the deal will be bringing some of its content, including live broadcast TV to Hulu. According To CNET:

The streaming service, which is owned by three of the country’s main broadcast networks, plans to launch a paid option next year that broadcasts live TV over the internet. On Wednesday, Time Warner’s Turner bundle of networks became the first to sign on. Turner channels to be available live on Hulu include TNT, TBS, CNN, Cartoon Network, Adult Swim, TruTV, Boomerang and Turner Classic Movies.
The deal doesn’t include Time Warner crown jewel HBO, and it doesn’t put any on-demand shows from the company on Hulu’s Netflix-like service of TV repeats and originals.

As a “cord cutter” I’m excited about this deal, but nervous that they may “screw it up.” Especially during big news events (like election season) one of the things I miss is having access to cable news, like CNN. CNN allows cable TV subscribers to stream their content live over Apple TV, but there’s currently no (authorized) way for those of us without subscriptions to access live content. I’ve long said I’d love the option to pay somewhere between $2 - $5 month to be able to buy streaming CNN as an a la cate option.

Depending on the final price of this plan with Hulu, it may be an option. I just hope they don’t screw it up by bundling together a bunch of channels and services I don’t care about and over-charging. That’s what ultimately keeps me from subscribing to services like Sling.

]]>
-
- - Mac Power Users #333: The Amazon Lifestyle - Katie Floyd - Tue, 02 Aug 2016 22:14:00 +0000 - http://tracking.feedpress.it/link/980/4045901 - 50c628b3e4b07b56461546c5:50c658a6e4b0cc9aa9ce4405:579f75a52e69cfd777c37618 - I love Amazon. It has become my default place for all my online shopping and has also replaced a lot of my local shopping as well. This week on Mac Power Users, David and I dive deep on the Amazon lifestyle. We explore many different services and features available from Amazon and share our favorite tips and tricks for how to make the most of them.

This episode of Mac Power Users is sponsored by:

  • Squarespace: Enter offer code MPU at checkout to get 10% off your first purchase.
  • Igloo: An intranet you’ll actually like, free for up to 10 people.
  • PDFpen from Smile With powerful PDF editing tools, available for Mac, iPad, and iPhone, PDFpen from Smile makes you a Mac Power User.
  • Fracture Bring your photos to life.

Permalink

]]> - - - ScreenCastsOnline Monthly Magazine: Review of Eero - Katie Floyd - Mon, 01 Aug 2016 16:10:55 +0000 - http://tracking.feedpress.it/link/980/4033735 - 50c628b3e4b07b56461546c5:50c658a6e4b0cc9aa9ce4405:579f74bc8419c2adac587a7f - This month’s issue of ScreenCasts Online Monthly Magazine is now available in ScreenCasts Online Magazine App. In the August issue you’ll find an article from me about Eero wireless access points

The monthly magazine is packed with streamable versions of Don’s excellent video tutorials as well as articles, reviews and tips from authors including David Sparks, Allison Sheridan, Wally Cherwinski and more. The magazine is free for ScreenCasts online Premium Members or available as a separate subscription or you can pickup individual issues. You can download it in the AppStore or find more info at https://www.screencastsonline.com/membership_benefits/

]]>
-
- - Katie's Week in Review: July 31, 2016 - Katie Floyd - Sun, 31 Jul 2016 21:47:57 +0000 - http://tracking.feedpress.it/link/980/4026944 - 50c628b3e4b07b56461546c5:50c658a6e4b0cc9aa9ce4405:579e71f020099e276ae4782d - This week saw Apple’s Third Quarter financial results and a billion iPhones sold. Here are the links of note for the week ending July 31, 2016:

  • Apple reported its Third Quarter Results. You can find the official report on Apple’s website. Rene Ritchie and Mikah Sargent of iMore have prepared a transcript of the call and Jason Snell of Six Colors has a slew of charts and commentary breaking down the quarter. Although the year-over-year numbers for iPhone were down, probably due to the amazing success of the iPhone 6 last year, Apple did manage to beat expectations for the quarter. The third quarter is traditionally Apple’s worst. Next quarter we’ll hopefully have a bump from “back to school” sales, but right now there’s not a lot of new product out there for people to buy.
  • Apple also announced this week that it sold its one billionth iPhone. Yes, that’s billion with a “B”. When Apple announced the iPhone less than 10 years ago Steve Jobs said they were just hoping to capture a portion of the phone market. Turns out they’ve managed to create a product that completely changed the world.
  • Speaking of the new iPhone, it seems like a pretty sure deal that the new iPhone released this fall will lack a headphone port. As reported by Jeff Gamet of The Mac Observer a new video making the rounds on the Internet claims to show Apple’s own Lightning to 3.5mm headphone jack adapter. While I have no doubt that Apple will release such an adapter or something similar, I doubt they will ship it in the box as doing so would 1) almost be an admission that removing the headphone jack might have come too soon and 2) negate the opportunity for add one sales. I bet we’ll see another $9.99 - $19.99 adapter as an opportunity for an add-on purchase.
  • As reported by Chance Miller of 9to5Mac, Apple has purchased the rights to ‘Carpool Karaoke’, a popular Late Late show segment and will release new episodes via Apple Music. Aside from Beats One, this is really the first time we’ve seen Apple purchase content for redistribution and seems like an odd move. We’ll see where this goes.
  • Daisuke Wakabayashi of the Wall Street Journal reports that Special Consultant and former Apple Executive Bob Mansfield will oversee Apple’s Car Project. Bob will supposedly be working directly with Jony Ive and senior managers on the car project are now reporting directly to Bob. If this is true, it sounds like things may be getting real.
  • While the Apple Car is likely a ways off, Harish Jonnalagadda reporting for iMore says that Ford will be integrating CarPlay into all of its 2017 vehicles. CarPlay will come as part of “Sync 3” which Ford has announced will be integrated in all of its 2017 cars, SUVs, light trucks and electric vehicles.
  • Kara Swisher of Recode reports that Verizon will buy Yahoo for $4.83 billion with the core business being merged into AOL. Verizon will keep the Yahoo brand. It’s hard for me to get excited about any Yahoo news since I’ve long since stopped using any of Yahoo’s core products or services, given the relatively low acquisition price it sounds like a lot of other people have too.
  • As reported by Thomas Reed of Malwarebytes Safe Mac Blog, there’s new cross-platform malware, Adwind, which can also infect Macs. The Malware requires the installation of Java, which does not come installed natively on the Mac, and isn’t code signed, so Gatekeeper will top it, but it’s out there nonetheless.
  • Adam Engst writing at TidBITS alerted me to another possible vulnerability. Apparently you can Disable Find My Mac by resetting the NVRAM which is ridiculously easy to do. This can be prevented by setting a Firmware password, which has it’s own risks and benefits. Things to know.
  • Finally this week, not really tech related (Okay, Netflix related) but one of my favorite TV Series of all time is Gilmore Girls. A new “Year in the Life” series is coming to Netflix as a 4-part special and premiers on November 25. I love all my favorite shows and movies are all coming back again. Let’s just hope this is better than the X-Files reboot. Here’s the teaser announcement:
]]>
-
- - Early Bird Registration Open for MacTrack Legal - Katie Floyd - Sat, 30 Jul 2016 15:39:53 +0000 - http://tracking.feedpress.it/link/980/4016962 - 50c628b3e4b07b56461546c5:50c658a6e4b0cc9aa9ce4405:579cca2046c3c4b32bed5f74 - - - - - - - - - - -

Formally known as MILOfest, MacTrack Legal is one of the the best tech and legal practice conference for Mac-loving lawyers. It is a conference for attorneys interested in Macs, iPhones, iPads and anything else bearing an Apple logo. The conference features an interesting mix of practical tips, substantive education.

This event gathers the most collegial and generous attorneys from across the country (and even overseas) in a fun and information multi-day program. The event will be held at Disney’s Yacht and Beach Club in Orlando, Florida October 6–8, 2016. I’m planning to attend and speak at the conference again this year and I hope to see some of you there.

Early Bird pricing is currently available and there’s a special discount if you’ve attended MILO fest at least twice in the past seven years. You can learn more at http://mactracklegal.com

]]>
-
- - Mac Power Users #332: Other Uses For Keynote - Katie Floyd - Tue, 26 Jul 2016 22:59:00 +0000 - http://tracking.feedpress.it/link/980/3980262 - 50c628b3e4b07b56461546c5:50c658a6e4b0cc9aa9ce4405:57975e9a46c3c49c534d4c9a - We all know Keynote is a great app for giving presentations but it can do so much more. This week on Mac Power Users, David and I explore the many uses for Keynote beyond presentation day.

This episode of Mac Power Users is sponsored by:

  • TextExpander from Smile Type more with less effort! Expand short abbreviations into longer bits of text, even fill-ins, with TextExpander from Smile.
  • 1Password Have you ever forgotten a password? Now you don't have to worry about that anymore. 
  • The Omni Group We're passionate about productivity for Mac, iPhone and iPad. 
  • Backblaze: The unlimited, native backup solution for your Mac and PC

Permalink

]]> - - - 10 Great Apps for Productivity - Katie Floyd - Tue, 26 Jul 2016 12:55:01 +0000 - http://tracking.feedpress.it/link/980/3976778 - 50c628b3e4b07b56461546c5:50c658a6e4b0cc9aa9ce4405:57975b4903596ebfc9a4d3c5 - I'm an attorney by day and others in my local legal and business community have come to know me as a "techie".  Just last week while at a meeting of the probate section meeting a colleague leaned over and wanted to know about several of the Apps on my iPad home screen.  As someone who is regularly asked about the apps I'm using for my work, I thought I'd put together a list of some of my favorites. To start, here are my ten of my favorite apps geared towards professionals. 

1. Due - $4.99 - http://www.dueapp.com - Perhaps I have a problem with reminder/task management apps. You’ll notice I several on this list, but they all serve a different purpose. Due is a reminders and timers App that I use for the specific purpose of reminding me for things that absolutely have to be done, or have to be done at a specific time. The problem with traditional reminder apps is you’ll typically get a notification of a task, but that’s it. If you’re busy at the moment the notification pops up, you’re likely to forget about ti and move on. Due will repeatedly remind you of items that are due until you act on them. You set the reminder interval. For extremely urgent items, you can set an interval as short as 1 or 5 minutes. For not so urgent items, you may only want to be reminded ever 30 minutes or an hour. 

- - - - - - - - - - - - - - - -

2. Scanner Pro - $3.99 - https://readdle.com - I go back and forth with scanner apps, but currently Scanner Pro is my favorite. I try to adopt a paperless lifestyle so whenever possible I try to scan receipts, papers and other bits of paper I come across in my life so I can get rid of them. Scanner Pro is a great all around scanner App but has recently added the ability to OCR documents and create a default name based on the date as well as create custom workflows for processing specific items. For example, I have a workflow that will process business receipts to a specific Dropbox folder, then delete the receipt once saved.

3. Fantastical - $4.99 – https://flexibits.com/fantastical-iphone - Fantastical has replaced the Calendar App on my iPhone for time management. The big feature of Fantastical is natural language parsing. I can type out a phrase such as “Lunch with David Tuesday at Taco Toms” and Fantastical will automatically create an appointment at noon on the next Tuesday and set the location as Taco Toms and attempt to locate and add the address of the restaurant. 

- - - - - - - - - - - - - - - -

4. Cloak VPN - Varies - http://www.getcloak.com if you use free Wi-Fi hotspots such as in coffee shops or at hotels and conference centers you need to use a VPN to secure your Internet traffic from prying eyes. Cloak is probably my favorite of the VPN solutions because they offer solutions for Mac and iOS and have a feature that will auto secure a connection to an unknown wireless network so I don’t have to think about it. As soon as I walk into Starbucks and connect, Cloak automatically activates and secures my connection. There are a couple of price options available. I keep a “mini” subscription active for a couple bucks a month that gives me 5GB of access each month which is good for occasional usage. If I’m traveling and need more data, I can add on data as needed.

5.Microsoft Office – Varies - https://products.office.com/en-us/mobile/office-iphone Microsoft Office is a suite of products that has been a pleasant surprise on iOS. I most often use Word.app on my iPad and have found it to be remarkably full featured and stable. For all devices except the iPad Pro (due to it’s screen size larger than 10") most of the features of the office suite such as editing and saving documents can be unlocked with a free Microsoft account. The products also integrate well with cloud services such as Dropbox and Microsoft’s own OneDrive.

- - - - - - - - - - - - - - - -

6. Duet Display - $9.99 - http://www.duetdisplay.com - I love having a second monitor and find it’s a great productivity tool. However, when I’m traveling or using my laptop on the go, that isn’t possible - unless you have Duet Display. This App allows you to use your iPad or iPhone as an extra display and will extend the desktop of both Macs and Windows PCs. 

7. PDFpen - $19.99 - https://smilesoftware.com/pdfpen-ios - I’ve tried several PDF apps on iOS and PDFpen is still my favorite, probably because it syncs so well with PDFpen on the Mac. Especially since I’ve upgraded to a 9.7" iPad Pro, I find myself viewing and editing PDFs more on the larger size iPad and viewing PDFs in split screen for reference while working in another document. I love PDFpen’s ability to edit PDFs and regularly find myself dropping my signature into PDFs on the go and sending them back to people.

- - - - - - - - - - - - - - - -

8. OmniFocus - $39.99/$59.98 - https://www.omnigroup.com/omnifocus - With recent updates OmniFocus on iOS has really come into its own as a standalone application rather than simply as a companion to the Mac App and it’s my task management system of choice. Just about anything that you can do in the Mac App can now be done in iOS and Omni has now even added support for automation which is great for setting up recurring complex tasks or a series of tasks. I’m a great fan of Siri integration which means OmniFocus can read items off my default Reminder’s list or a specific Reminder’s list and when items are added to that list (either through Siri or otherwise) they will automatically be added to my OmniFocus inbox.

9. Deliveries - $4.99 - http://junecloud.com/software/iphone/deliveries.html - With the exception of weekly grocery shopping, I’ve probably transitioned most of my shopping online. I buy almost all my electronics, clothing and convenience items online. This means I have a lot of packages coming to my door and sometimes can loose track of them. I use Deliveries to track shipments so I know when to expect them. My favorite feature is after buying the App you can sign up for a free account on their website, register your email address and from there you can forward delivery notification emails to a special email address and the delivery information will automatically show up in the app. It’s almost too easy.

- - - - - - - - - - - - - - - -

10. Todoist - Free/$28.99 a year - http://www.todoist.com - This is my third task management related App on this list, but it serves a specific purpose. While I love OmniFocus for my personal task management needs, I was missing a way to share and delegate tasks with others, particularly in the office. To solve this need, our office turned to Todoist. As a task management App it’s pretty basic, and candidly I’m not in love with the design or implementation. But the sharing and collaboration features are good and it allows me to share projects with other attorneys in my office as well as my legal assistant and assign tasks and check on the status of those tasks. For less than $30 a year per person for a business plan, the price was right to get our office started.

This article first appeared in the June Issue of ScreencastsOnline Monthly Magazine. ScreenCastsOnline monthly magazine is packed with hints, tips, articles and links to streamable versions of ScreenCastsOnline tutorials and delivered monthly via Newsstand on the iPad. You can find out more at https://www.screencastsonline.com/membership_benefits/

]]>
-
- - Katie's Week In Review: July 24, 2016 - Katie Floyd - Sun, 24 Jul 2016 19:51:38 +0000 - http://tracking.feedpress.it/link/980/3960924 - 50c628b3e4b07b56461546c5:50c658a6e4b0cc9aa9ce4405:57951c398419c25e8f05f8cd - We’re still in the dog days of summer, but there’s a little more Apple news to report. Here are the links of note for the week ending July 24, 2016:

  • Apple has released a slew of software updates this week including OS X 10.11.6, iOS 9.3.3, tvOS 9.2.2 and watchOS 2.2.2. The updates include primarily bug fixes but also several security updates. The downloads can be obtained through the software updated function on your hardware. I’ve updated all my devices and haven’t run into any issues.
  • Apple announced its FY 16 Third Quarter Results Conference Call will be held on Tuesday, July 26th at 2pm Pacific, 5pm Eastern. The call will be available as a webcast and also available for two weeks after for replay on Apple’s website. Investors are a bit nervous about this call given Apple’s lackluster results last quarter and the somewhat negative rumors circulating about Apple’s upcoming iPhone 7. Let’s hope cooler heads prevail.
  • Speaking of the new iPhone 7, Juli Clover of MacRumors reports on a new mockup circulating around the Internet which depicts three models of the new iPhone including an iPhone 7, iPhone 7Plus and a new iPhone 7 Pro. The Pro model allegedly features a 5.5" form factor, dual lens and a smart connector. I’m personally a little skeptical about this rumor, but time will tell.
  • There was major acquisition news this week, as reported by Dan Frommer of Recode, SoftBank is reportedly buying ARM Holdings for $32 billion. This is notable not only for the size of the acquisition, but also because ARM chips dominate the smartphone market including Apple’s iPhone and iPads.
  • There have been several complaints since iTunes Match launched that the service was replacing “matched” songs with different versions. For example, users have reported live tracks could be swapped with studio versions or similar mistakes. Supposedly Apple has been working on audio fingerprint technology to improve the accuracy of this match. As Jim Dalrymple of the Loop explains, Apple has already been using audio fingerprint technology for iTunes Match subscribers and will now be rolling it out to Apple Music Subscribers. I subscribe to iTunes Match, but not Apple Music. I still see a frustrating number of unmatched tracks for no apparent reason, but hopefully as the technology improves this will decrease.
  • The popular writing App Scrivener is now available for the iPad and iPhone. We’ve spoken about Scrivener a lot on Mac Power Users, my pal David Sparks has written books on Scrivener and shares his thoughts on the new iOS App. (Spoiler alert, he likes it.)
  • Jeff Gamet of The Mac Observer has news that the new Star Trek Series is coming to Netflix Streaming, but not in the US. The new series will launch in 2017, but US viewers will need a paid CBS All Access account that costs $6 per month. Still, good news for those in the 188 countries where Netflix will be showing the series.
  • Drafts is one of my favorite iOS Apps, in fact its in my Dock, but I still don’t know many of the ninja tricks. Good news, my friend David Sparks has been working on a Screencast Series for Draft with over an hour of content. The series is now complete and there are over 15 tutorials in total available on the Drafts website. Here’s a sample:
]]>
-
- - Mac Power Users #331: Stephen Hackett: Collector of Macs - Katie Floyd - Thu, 21 Jul 2016 16:09:44 +0000 - http://tracking.feedpress.it/link/980/3937419 - 50c628b3e4b07b56461546c5:50c658a6e4b0cc9aa9ce4405:5790f38603596ea40a37aebd - This week on Mac Power users RelayFM co-founder, Apple Collector and YouTuber Stephen Hackett joins David and me to explain essential cloud-based services used to run the Relay Network, his growing collection of Macs, and his photo and video workflows.

This episode of Mac Power Users is sponsored by:

  • Hover: Simplified domain management. Use code AUTOMATION to get 10% off your first purchase.
  • The Omni Group We're passionate about productivity for Mac, iPhone and iPad. 
  • Automatic The connected car company that improves your driving and integrates your car into yoru digital life. Enter code MACPOWER to get 20% off your purchase. 
  • Fracture Bring your photos to life.

Permalink

]]> - - - Katie's Week in Review: July 17, 2016 - Katie Floyd - Sun, 17 Jul 2016 22:11:55 +0000 - http://tracking.feedpress.it/link/980/3903182 - 50c628b3e4b07b56461546c5:50c658a6e4b0cc9aa9ce4405:578c02988419c2c106ad40d3 - My apologies for skipping the Week in Review last week, but I had a fabulous time at my brother’s wedding. To make up for it, I have a little extra content this week, so without further delay, here are the links of note for the week ending July 17, 2016:

  • This week we saw a leaked video that allegedly shows the iPhone 7 rear casing. As noted by Juli Clover of MacRumors, the design features no headphone jack and a larger camera hole that may accommodate an improved image sensor.
  • Eddie Cue spoke with Natalie Jarvey of the Hollywood Reporter and shared some insight about future plans for the Apple TV and thoughts on video streaming services. Among other things, Cue says Apple isn’t in the business of trying to create TV shows but is open to “helping guide them and make suggestions.”
  • Apple has released public betas of iOS 10 and macOS Sierra. Although I recommend extreme caution when installing betas (and I suggest you don’t do this on any machine you rely on) Serenity Caldwell and Lory Gil of iMore has released guides on how to install iOS 10 and macOS Sierra public betas. If you decide to play with the beta, Jason Snell of Six Colors has 7 things to try in the macOS beta.
  • If you weren’t able to attend WWDC, Apple has released the videos of the sessions along with searchable transcripts. Watch and enjoy!
  • A new piece of Mac malware has been discovered. Thomas Reed writing for the Malwarebytes blog explains that this new malware is in the form of a fake document converter app that actually installs a backdoor to your Mac. As always, download software with caution.
  • Last week Apple announced it was bringing the National Organ Donor Registration to iPhone as part of an update to iOS 10. With the new released iPhone users will be able to sign up to be an organ, eye, and tissue donor from the Health.app. I’m already registered as an organ donor, but I’ll be updating my information in the Health App as soon as the new OS is released
  • I’m a big fan of two-factor authentication and try to enable it for all my apps and services. Recently Apple has changed the way they implement two-factor authentication. If you’re using the old system (a four digit code delivered via push notification or SMS) you may want to update to the new and improved method. If your confused (as I was) have no fear, Dan Moren writing at Six Colors walks you through the process.
  • This week Pokémon Go! swept across the world. I’m not a big gamer and have never had much of an interest in Pokémon, but it seems to be all the Internet can talk about. If your curious about what the rage is all about, Serenity Caldwell, Lory Gil and Jen Karner of iMore have created a Beginner’s guide on how to play Pokémon Go.
  • My thanks to MailButler for sponsoring KatieFloyd.com this week. MailButler is an add-on, which adds many great productivity boosting features to your Apple Mail. If you’re interested in promoting your App or service through a RSS sponsorship of this site, you can learn more here.
  • Finally, Stephen Hackett (who is our guest next week on MPU) has continued his video series. This time Stephen reviews 7 old Apple notebooks in 6 minutes.
]]>
-
- - Mac Power Users #330: I'm not familiar with "Wookieepedia" - Katie Floyd - Tue, 12 Jul 2016 23:45:00 +0000 - http://tracking.feedpress.it/link/980/3859869 - 50c628b3e4b07b56461546c5:50c658a6e4b0cc9aa9ce4405:5782ec45b8a79b369b3af426 - This week on Mac Power Users David and I catch up on listener feedback. MPU Listener Ruben shares how he uses his MPU workflows on his work PC, we discuss merging Apple IDs, cleaning up after being scammed, follow-up on iOS photography, family tech support and listeners share their tips and tricks.

This episode of Mac Power Users is sponsored by:

  • Igloo: An intranet you’ll actually like, free for up to 10 people.
  • Fujitsu ScanSnap ScanSnap Helps You Live a More Productive, Efficient, Paperless Life. 
  • Gazelle Sell your iPhone for cash at Gazelle! 
  • Sanebox Stop drowning in email!

Permalink

]]> - - - diff --git a/RSParser/Tests/RSParserTests/Resources/OneFootTsunami.atom b/RSParser/Tests/RSParserTests/Resources/OneFootTsunami.atom deleted file mode 100755 index ecf9c69fd..000000000 --- a/RSParser/Tests/RSParserTests/Resources/OneFootTsunami.atom +++ /dev/null @@ -1,673 +0,0 @@ - - One Foot Tsunami - Slightly less disappointing than it sounds - - 2015-09-08T14:21:41Z - - - http://onefoottsunami.com/feed/atom/ - - - WordPress - - - - Paul Kafasis - - Link: <![CDATA[Pillow Fight Leaves 24 Concussed]]> - - - - - http://onefoottsunami.com/?p=14863 - 2015-09-07T18:14:11Z - 2015-09-08T14:21:41Z - At West Point, freshman cadets have long had an annual massive nighttime pillow fight to build esprit de corps. This year, it turned violent.


∞ Permalink]]> - - - - - - - Paul Kafasis - - <![CDATA[Perverse Incentives]]> - - http://onefoottsunami.com/?p=14861 - 2015-09-07T12:21:22Z - 2015-09-07T12:21:01Z - In China and Taiwan, drivers who’ve hit someone with their car may attempt to kill the person. Why?

- -

[I]f you cripple a man, you pay for the injured person’s care for a lifetime. But if you kill the person, you “only have to pay once, like a burial fee.”

- -

Because the legal system has often failed to prosecute these murders, a perverse incentive has been created. Once a driver hits an individual, the financially prudent move is for him to kill the injured party, rather than allowing them to live with a severe injury.

]]>
-
- - - - - Paul Kafasis - - Link: <![CDATA[Space Jam Forever]]> - - - - - http://onefoottsunami.com/?p=14858 - 2015-09-04T14:31:25Z - 2015-09-04T14:31:31Z - In 2010, a user on Reddit discovered that the website for the 1996 movie “Space Jam” was inexplicably still online. Almost 5 years later, and nearly 20 years after the film was released, that’s still true. It’s a wonder to behold. Now, Rolling Stone has done a wonderful archaeological dig on a piece of the ancient Internet, well preserved.


∞ Permalink]]>
-
- - - - - - Paul Kafasis - - <![CDATA[Head of the Charles]]> - - http://onefoottsunami.com/?p=14854 - 2015-09-03T03:34:24Z - 2015-09-03T14:32:12Z - As America grew in the 1800 and 1900s, many of our waterways became incredibly polluted. Cleveland’s Cuyahoga River actually managed to catch fire on multiple occasions. Likewise, Boston’s Charles River was famously polluted by both sewage runoff and industrial wastewater. That impression of a foul waterway is now deeply engrained in the minds of locals, reinforced constantly by the oft-heard classic Standells’ song “Dirty Water”.

- -

However, Herculean efforts made since 1995 have improved the river’s quality. Recently folks have even begun swimming in the Charles again, at least when water quality permits it. The EPA has tracked the steady improvement, now rating the Charles as one of cleanest urban rivers in America.

- -

'I Swam The Charles' Bumper Sticker -
[Photo courtesy of P. Kafasis]

- -

Still, does anyone actually want to drink water from the Charles River? Boston-based beermaker Harpoon aims to find out. Their new “Charles River Pale Ale” contains a not-so-secret ingredient: 300 gallons of Charles River water. While locals are likely gagging and reflexively spitting at the very thought, Harpoon is assuring the public the suds will be both safe and delicious.

- - -

In fact, the water was treated by Desalitech, a local water desalination company, purified to make it ideal for drinking. So taking a little taste of the Charles, at least in this form, probably won’t kill you. And hey, if/when you survive the experience, you can wear a shirt in this vein:

- -

Milton from Office Space in a shirt reading 'I Ate The Worm!!' -
I Drank the River!!

]]>
-
- - - - - Paul Kafasis - - Link: <![CDATA[No More Sense Than an Amish Bus Driver]]> - - - - - http://onefoottsunami.com/?p=14852 - 2015-09-02T17:32:10Z - 2015-09-02T17:31:10Z - Back in June, supporters of marriage equality declared victory in America with the Supreme Court’s ruling on Obergefell v. Hodges. Of late, Kentucky county clerk Kim Davis has been making news by defying the authority of the highest courts in the land, refusing to issue any marriage licenses whatsoever. Surprise surprise, Ms. Davis is a tremendous hypocrite, as US News reports:

- -

The marriages are documented in court records obtained by U.S. News, which show that Rowan County Clerk Kim Davis divorced three times, first in 1994, then 2006 and again in 2008.

- -

She gave birth to twins five months after divorcing her first husband. They were fathered by her third husband but adopted by her second. Davis worked at the clerk’s office at the time of each divorce and has since remarried.

- -

If Ms. Davis doesn’t wish to do her job, she should resign or expect to be removed, as John Corvino ably discusses:

- -

If [Davis’s] conscience renders her unable to issue marriage licenses to those legally qualified, then the right thing for her to do is resign. After all, issuing marriage licenses is not a peripheral, non-essential part of being county clerk — it’s a central job function. Her current stance makes no more sense than that of an Amish person who expects to retain a job as a bus driver.

- -

Here’s hoping this odious woman is found in contempt of court tomorrow, and quickly removed from her job, or at least from the spotlight.


∞ Permalink]]>
-
- - - - - Paul Kafasis - - Link: <![CDATA[Self-Driving Cars vs. Fixed-Gear Bikes]]> - - - - - http://onefoottsunami.com/?p=14850 - 2015-09-01T16:22:03Z - 2015-09-01T16:22:00Z - When a rider on a fixed-gear bicycle arrived at an intersection with a Google self-driving car, the car didn’t quite know what to do.


∞ Permalink]]>
-
- - - - - Paul Kafasis - - Link: <![CDATA[That’s Not What Dolphins Do]]> - - - - - http://onefoottsunami.com/?p=14848 - 2015-08-31T15:38:01Z - 2015-08-31T15:37:47Z - Surfer Elinor Dempsey didn’t catch any waves when she hit the ocean on Saturday, but she did get a pretty good story. As she waited in the water, something approached her from underneath.

- -

“First I thought it was a dolphin and I thought, ‘What the hell is he doing?’ ” she said. “And he kind of landed on my board. Then I realized he had taken a chunk. And I was, like, that’s not what dolphins do.”

- -

Shark Bite photo -
Better the board than her hand

- -

That is indeed not what dolphins do.


∞ Permalink]]>
-
- - - - - Paul Kafasis - - Link: <![CDATA[It’s Also a Very High-End Hunting Load for Ducks, Geese, or Turkeys]]> - - - - - http://onefoottsunami.com/?p=14846 - 2015-08-28T00:41:21Z - 2015-08-28T14:41:06Z - Well of course company is selling ammunition specifically marketed for shooting down drones. Of course they are.


∞ Permalink]]>
-
- - - - - Paul Kafasis - - Link: <![CDATA[Waste Not, Want Not]]> - - - - - http://onefoottsunami.com/?p=14839 - 2015-08-27T17:16:50Z - 2015-08-27T17:15:34Z - What do you do when you buy an expensive bottle of cognac but you’re not permitted to take it on your flight? Maybe you throw it out. You might gift it to a stranger. Perhaps you could throw an impromptu party in the airport. You’d be sure to make some new friends. Whatever you do, avoid the path taken by one Ms. Zhao:

- -

[S]he sat down in a corner and drank the entire bottle of cognac herself.

- -

As you’d probably imagine, this did not turn out well.


∞ Permalink]]>
-
- - - - - - Paul Kafasis - - <![CDATA[At Least He Can Still Go to the Same Church]]> - - http://onefoottsunami.com/?p=14836 - 2015-08-26T15:43:55Z - 2015-08-26T14:43:17Z - We open with a montage of newspaper articles, quickly showing JIMMY McGINTY’s criminal arc. - -

JIMMY is caught. -
(“NOTORIOUS MOB KILLER JIMMY McGINTY ARRESTED!”)

- -

He turns state’s evidence and aids the prosecution. -
(“McGINTY TESTIFIES AGAINST LEFTY HANNIGAN”)

- -

The trial brings down the last vestiges of the Boston Mafia. -
(“LEFTY HANNIGAN SENTENCED TO LIFE”)

- -

Finally, he disappears. -
(“WHERE IS JIMMY McGINTY NOW?”)

- -

CUT TO:

- -

INT. STEREOTYPICAL ITALIAN RESTAURANT – NIGHT

- -

Open on the CHEF, a older man with pale, freckled skin and bright red hair noticeably peeking out from under his chef’s hat. He is wearing an apron and a bushy mustache that is quite clearly fake.

- -

The CHEF approaches—

- -

A TABLE dressed in a red checkered tablecloth, with a candle lit atop it.

- -

—Where a lone female CUSTOMER, sits wearing a simple gray dress. She is persuing the menu.

- -
- -

CHEF -
(in a ridiculously over-the-top Italian-American accent — think “It’sa me, Mario!”) -
Buonasera, bella! What-a you like-a to have tonight?

- -

CUSTOMER -
(hesitant) -
Well, I’m not sure. I thought this was an Italian restaurant…

- -

CHEF -
(with delight) -
Oh, sì, sì! It is, it is!

- -

CUSTOMER -
(still hesitant) -
But I don’t recognize any of these dishes. “Black pudding”? “Limerick Ham”? “Corned Beef and Cabbage”, now that’s an Irish dish!

- -

CHEF -
(shaken, slips into a very real Irish-American accent) -
Ah, no, no, cailín -
(Quickly recovering his over-the-top Italian-American accent) -
Err, we Italians have-a that as well! But I-a tell you what. I’m-a gonna make you the specialty of the house! You-a trust me, no? After all, it’sa me, Mario! I own-a this place!

- -

CUSTOMER -
(relieved) -
Well, alright. That sounds lovely. Thank you, Mario!

-
- -

MARIO walks quickly to the back, through the inward swinging right kitchen door, then immediately back out the outward swinging left kitchen door with a tray he carries with two hands.

- -

On the tray is a plate which appears to contain a large tortilla covered in chunky tomato soup, with grated orange American cheddar cheese cooked on top. It is a comically poor imitation of Italy’s most famous dish.

- -

MARIO places the tray on the CUSTOMER’S table with a flourish.

- -
- -

CHEF -
Buon appetito!

- -

CUSTOMER -
(Staring at the plate, extremely hesitant) -
Uh… -
(Now staring intently at “MARIO”, noticing his red hair and fake mustache) -
What do you call this dish, “Mario”?

- -

MARIO -
(a ridiculously over-the-top Italian-American accent) -
Ah, you are not-a the first person to ask! In fact, a-so many a-people ask, I name-a the restaurant after a-my reply!

- -

PULL BACK TO RESTAURANT EXTERIOR, REVEALING THIS SIGN

-
-

That'sa Pizza!

]]>
-
- - - - - Paul Kafasis - - Link: <![CDATA[Tomato Seasoning]]> - - - - - http://onefoottsunami.com/?p=14832 - 2015-08-25T04:38:31Z - 2015-08-25T13:38:17Z - Over in Israel, Heinz is no longer allowed to sell its most popular condiment as “ketchup”.

- -

Heinz will no longer be allowed to label its red sauce as “ketchup” in Hebrew in Israel after local food manufacturer Osem successfully argued that its competitor’s product doesn’t meet the definition of Israel’s standards institute, Israeli news site Ynet reported.

- -

Instead, Heinz must use the ridiculous euphemism “tomato seasoning” to label their product. Then again, given that “tomato seasoning” sounds like the classy way a fancy restaurant would refer to the side they bring with their “pomme frites”, I’m not sure how much of a punishment this really is.


∞ Permalink]]>
-
- - - - - Paul Kafasis - - Link: <![CDATA[“I Love the Thing That I Most Wish Had Not Happened”]]> - - - - - http://onefoottsunami.com/?p=14829 - 2015-08-24T07:23:02Z - 2015-08-24T13:40:35Z - GQ has a rather wonderful piece on the once and future Stephen Colbert, discussing the future of late night, a past full of loss, and being present in the present.


∞ Permalink]]>
-
- - - - - Paul Kafasis - - Link: <![CDATA[The UK’s Most Disappointing New Visitor Attraction]]> - - - - - http://onefoottsunami.com/?p=14825 - 2015-08-21T14:26:07Z - 2015-08-21T13:51:54Z - I’ve always found guerrilla artist Banksy interesting enough, particularly when selling his own pieces as fakes. However, his new creation is really something else. Over in England, he’s created a dystopian theme park named Dismaland, and it’ll be open to the public for the next month. Christopher Jobson reports in detail:

- -

The event has all the hallmark details of a traditional Banksy event from a shroud of ultimate secrecy (the event area was plastered in notices designating it as filming location for a movie titled Gray Fox) to general themes of apocalypse, anti-consumerism, and anti-corporate messages. However there’s one major deviation: the emphasis of Dismalanded is largely on other artists’ work instead of Banksy himself.

- -

This trippy piece alone is enough to make me jealous of those who can go:

- -

Ariel, Sort Of -
Do not adjust your eyeballs.

- -

I have to imagine Disney’s lawyers will be all over this whole thing. Heck, the park even bans them (“The following items are strictly prohibited: knives, spray cans, illegal drugs, and lawyers from the Walt Disney corporation.”). If you have a chance to go, it definitely seems worth it.


∞ Permalink]]>
-
- - - - - - Paul Kafasis - - <![CDATA[Pretty Damned Good for Around a Thousand Pixels]]> - - http://onefoottsunami.com/?p=14814 - 2015-08-21T03:21:12Z - 2015-08-20T13:43:08Z - Back in October, I wrote about Square Cash, my favorite service for both exchanging money with friends as well as being amused by the imagined negotiating process of an complete moron. Last year’s post provided me with a brief trickle of $1 referral bonuses, netting me something like $18, so naturally I’ve been itching for another chance to write about Square Cash. Square has since upped their referral bonus to $5 for both sides, so, ya know: Sign up for Square Cash and get yourself a Lincoln.1

- -

Anyhow, the Square Cash iPhone app was recently updated to include support for the Apple Watch. Our glorious future truly has arrived, because it’s now possible to send money to your friends right from your wrist. Open the app and tap your desired recipient to see a screen like this:

- -

The Apple Watch Square Cash app
“Select Amounts” is kind of a weird instruction.

- -

To send cash, you tap the relevant bills to add up to the desired whole number (no change!) you wish to send, then tap “Pay”. Within seconds, and without any further verification or chance of cancelling, your money will be flying off to someone else’s bank account. As your money wings away, there’s even a ridiculous animated image of dollar bills fluttering down.

- -

The Apple Watch Square Cash app
No matter how long you stare at this image, it won’t move, because it’s just a still. Feel free to cash $PBones to see the full animated version though.

- -

I’ve previously written that you do not have to make an Apple Watch app. However, good third-party apps for the watch are certainly possible.2 The Square Cash watch app is definitely well made, and it offers functionality I’ll describe as at least potentially useful, which means it’s better than most Apple Watch apps to date. Perhaps the best thing the Square Cash Apple Watch app does, however, is advance the cause of gender equality.

- -

Allow me to back up slightly. You may have seen a recent push to put Harriet Tubman on America’s $20 bill, fully replacing Andrew Jackson. Hey, according to this article, Old Hickory might not have minded the change:

- -

[Jackson] also hated paper currency and vetoed the reauthorization of the Second Bank of the United States, a predecessor of the Federal Reserve.

- -

This proposed change has also led to other women being considered for placement on American currency, and it appears that the next re-design of the $10 bill will at least provide Alexander Hamilton with a female co-star. That’s some progress, at least, but the idea of placing Harriet Tubman on the $20 has also laid bare some incredible stupidity. Take a deep breath and try to absorb this:

- -

Jimmy's Stupid Comment
I think it’s the exclamation point that really gets me.

- -

Oh jeez. I honestly try to avoid highlighting this sort of depressing idiocy. I figure it’s best to let it die quietly in the dark, rather than than live and spread in the light. Some things are just so feebleminded that they must be skewered, however, and this is one of those things. So, how dumb are you, Jimmy Pecoul? Let me count the ways.

- -

A list of the ways in which Jimmy Pecoul has shown off his ignorance, in increasing order of stupidity

- -
    -
  • Problem #1: Thinking that only presidents belong on our banknotes

    - -

    While this is wrong, I wouldn’t be surprised if a not-insignificant number of people believe this, and think that both Alexander Hamilton (on the $10 bill) and Benjamin Franklin (on the $100 bill) were presidents.

  • - -
  • Problem #2: Mistaking Harriet Tubman for Rosa Parks

    - -

    These are two entirely different woman, who are famous for their work winning progress in different areas (abolitionism for Harriet Tubman and civil rights activism for Rosa Parks), and who were active nearly a full century apart.

  • - -
  • Problem #3: Having no understanding of what Rosa Parks did

    - -

    Rosa Parks did not “stand up to bullies on a bus”. Rosa Parks defied a despicable law and set off the Montgomery bus boycott, which helped bring about the end of segregation in America.

    -
  • - -
  • Stupidity #4: Thinking he’ll stop using $20 bills

    - -

    I like to imagine how this might go. Jimmy would have to avoid just about every ATM in America, for one thing. The interactions with cashiers, waiters, bartenders and the like ought to be something to see as well. I’d give him a week managing to boycott the bill, and that’s being generous.

    -
  • - -
  • Stupidity #5: Believing that “most” people will stop using the $20 bill

    - -

    I doubt even one person in the entire country would stop using yuppie singles if the picture on them changed from Jackson to Tubman. Most? MOST? Jesus Crist.

    -
  • - -
-

We’ll just ignore the incredibly foolish acts of posting this publicly to Facebook for the whole world to snigger at, as well as thinking anyone gives a single good goddamn what his “vote” is on this matter, because if we don’t my head might explode. Let’s get back to Harriet Tubman and Square Cash (Square Cash!). Have another look at the buttons for selecting the amount of money you wish to send:

- -

The Apple Watch Square Cash app's buttons

- -

Each button features a pretty good portrait of the corresponding man who appears on that denomination’s bill, but Andrew Jackson on the $20 doesn’t look quite right.

- -

The face on Square Cash's $20 bill

- -

Of course, that’s not Andrew Jackson at all — it’s Harriet Tubman! The image appears to be based on an 1895 portrait of Mrs. Tubman which is part of the collection of America’s National Portrait Gallery. Here’s a side-by-side comparison:

- -

Pixel Tubman and Photo Tubman, side by side

- -

And here it is, blown up:

- -

Enlarged Pixel Tubman and Photo Tubman, side by side -
The pixel version has managed to turn that dour frown upside down.

- -

Not bad! Lest you have any lingering doubt as to the true identity of this image, Square has confirmed that the image does indeed represent Tubman, with a spokesman stating “We put Harriet Tubman on the $20 bill because she is an American hero”. Well done. It’s a small gesture, but it’s a good one nonetheless.

- -
- -

Update (August 20th, 2015): Square’s creative director Robert Anderson used his own accidental invention (the @-reply) to link me to a higher resolution version of the Tubman image. He also confirmed that the 1895 portrait seen above was indeed the inspiration for the cartoon version. Neat!

- -

A higher-res $20 Tubman -
A higher-res Tubman Twenty

- -
- -

Footnotes:

-
    -
  1. The bill, not the McConaughey-endorsed vehicle. -↩︎

  2. -
  3. I should note that the Apple Watch app from USAA has been updated since I mocked it in that post. Now, in addition to showing your account balance (and allowing you to refresh that account balance), the app will show any transactions from the last seven days. That’s actually mildly useful! -↩︎

  4. -
]]>
-
- - - - - Paul Kafasis - - Link: <![CDATA[Early Notes on the Ashley Madison Hack]]> - - - - - http://onefoottsunami.com/?p=14809 - 2015-08-19T16:13:25Z - 2015-08-19T16:13:24Z - Over at The Awl, John Herman writes about the Ashley Madison hack. The data from this hack appears to be close to being easily searchable by the public, and while the consequences of that have been joked about, they deserve closer consideration:

- -

I’m not sure anyone is really reckoning with how big this could be, yet. If the data becomes as public and available as seems likely right now, we’re talking about tens of millions of people who will be publicly confronted with choices they thought they made in private…Here were millions of people expecting the highest level of privacy that the commercial web could offer as they conducted business they likely wanted to keep between two people (even if a great number of the emails are junk, or attached to casual gawkers, the leak claims to contain nine million transaction records). This hack could be ruinous—personally, professionally, financially—for them and their families.

- -

While it would be easy to say that the people who used this site deserve whatever happens, the fallout from this hack will affect far more than just the users and the implications for the future are also well-worth considering.


∞ Permalink]]>
-
- - - - - Paul Kafasis - - Link: <![CDATA[Unlikely to Be a Viable Alternative]]> - - - - - http://onefoottsunami.com/?p=14807 - 2015-08-18T17:11:58Z - 2015-08-18T17:11:55Z - Police departments in Canada and the US are experimenting with a new, less-than-lethal use-of-force option. A device called “The Alternative” allows an officer to fire a single shot gun which may take down a suspect, without killing them. If the shot fails to take down the suspect, the officer’s gun returns to its normal, lethal state.

- -

It’s an interesting enough idea, and the physics of the device itself seem practical. However, it’s difficult to imagine officers attaching the device to their service weapon in the middle of a confrontation. Perhaps even less likely is police departments adopting a device nicknamed the “Clown Gun”.

- -

The Clown Gun Explained -
The silver ball was originally bright orange and resembled a clown’s nose.


∞ Permalink]]>
-
- - - - - Paul Kafasis - - Link: <![CDATA[How to Win Contests and Influence Mexican Soap Stars]]> - - - - - http://onefoottsunami.com/?p=14805 - 2015-08-17T17:37:20Z - 2015-08-17T17:37:14Z - Hunter Scott won a whole lot of contests via Twitter, all thanks to a bot.

- -

My favorite thing that I won was a cowboy hat autographed by the stars of a Mexican soap opera that I had never heard of.

- -

Few of the prizes were valuable, but the whole experiment is amusing, and that’s worth something.


∞ Permalink]]>
-
- - - - - Paul Kafasis - - Link: <![CDATA[Chicago Falcons]]> - - - - - http://onefoottsunami.com/?p=14803 - 2015-08-14T04:59:10Z - 2015-08-14T13:59:12Z - If you want to see ridiculous good photos of peregrine falcons living on a balcony in Chicago, look no further.

- -

Peregrine Stalking Image -
Peregrine Falcon on Patrol


∞ Permalink]]>
-
- - - - - Paul Kafasis - - Link: <![CDATA[You Reap What You Sow]]> - - - - - http://onefoottsunami.com/?p=14801 - 2015-08-13T04:52:36Z - 2015-08-13T13:44:30Z - Megyn Kelly is getting the short end of the stick from her network as they work to sooth the hurt feelings of one Donald Trump. It’s a deplorable situation all around, but at the same time, what did Kelly expect when she went to work for Fox “News”?


∞ Permalink]]>
-
- - - - - - Paul Kafasis - - <![CDATA[Please Report to the Principal’s Office]]> - - http://onefoottsunami.com/?p=14784 - 2015-08-11T18:30:28Z - 2015-08-12T14:34:52Z - Speaking of anxiety-inducing email subject lines, here’s a doozie:

- -
  • A chat about your bad photos?

- -

When Apple removed the Camera Roll feature of iOS, I briefly used an app called MyRoll. Now, the company is emailing me about a new app called Gallery Doctor, which claims to identify and help you remove bad photos, thereby saving you space on your iPhone. That’s great and all, but how about an uplifting introduction, instead of an email that feels like it’s chastizing me?

]]>
-
- - - - - Paul Kafasis - - Link: <![CDATA[There Is, of Course, No Gun]]> - - - - - http://onefoottsunami.com/?p=14798 - 2015-08-11T00:09:01Z - 2015-08-11T13:08:56Z - Michael Heald has written an incredible story of running a half-marathon behind the walls of Oregon State Penitentiary, where the phrase “The Wall” takes on a very different meaning.


∞ Permalink]]>
-
- - - - - Paul Kafasis - - Link: <![CDATA[Unwelcome and Superfluous]]> - - - - - http://onefoottsunami.com/?p=14796 - 2015-08-10T15:56:03Z - 2015-08-10T15:55:54Z - Writer Ralph Jones gets a lot of press releases. Recently, he started replying to all of them with the phrase “I love you”.


∞ Permalink]]>
-
- - - - - - Paul Kafasis - - <![CDATA[Get Off My Back, CVS]]> - - http://onefoottsunami.com/?p=14754 - 2015-08-02T15:49:38Z - 2015-08-07T14:16:37Z - Sometimes I receive an email that fills me with existential sorrow. An email with a subject like:

- -
  • Paul, Are You Making the Most of Our App?

- -

Well god, CVS, I guess I really just don’t know. Am I? The implication is clearly that I’m not making the most of your goddamned app. I don’t need this pressure though, man. Hell, you’re clearly aware that I have the app.

- -

CVS Image

- -

But that’s not enough for you, is it? You’ve gotta tell me about the features of an app I already have installed and insist that I use them. But I’m not in the habit of printing out many photos anymore, and I’m not on any pills. You just let me use my iPhone as I like, and I’ll continue buying the assorted cold medicine, greeting cards, and clandestine bars of candy that I usually do. Deal?

]]>
-
- - - - - Paul Kafasis - - Link: <![CDATA[Drones Are Delivering Contraband to Prisons]]> - - - - - http://onefoottsunami.com/?p=14789 - 2015-08-06T14:43:29Z - 2015-08-06T14:43:28Z - Well of course drones are being used to deliver illicit goods into prisons.


∞ Permalink]]>
-
- - - - - - Paul Kafasis - - <![CDATA[Planes, “Planes”, and Automated Fare Pricing]]> - - http://onefoottsunami.com/?p=14756 - 2015-08-05T16:59:42Z - 2015-08-05T15:50:23Z - Let’s say that you wanted to get from Dayton, Ohio to central New Jersey, as friend of the site Chris DiNoia recently wanted to do. On United.com, you might select Newark as your destination, and get a result like this:

- -

Flying into Newark

- -

$353 is rather pricey for a one-way flight. Let’s check some other options. Philadelphia is about 30 minutes farther than Newark from central Jersey, and presumably you’re not a defenseless robot, so you should be safe there for a few minutes. Set Philadelphia as your destination instead, and hey, why not turn on the “Search Nearby Airports” checkbox?

- -

Flying into Philadelphia

- -

Hey, now there’s a better deal. You can get home for just $149! But hang on a sec. A close look shows that rather than Philadelphia International Airport (PHL), this flight winds up at “ZFV”, which is labeled as a “rail station”. How exactly is a plane going to land there?

- -

Examine this flight, and you’ll see something bizarre:

- -

Train Service -
“NOTE: This is Train Service” is a truly amazing warning.

- -

United Airlines is apparently code-sharing with Amtrak’s passenger railroad service to get you to Philadelphia. They’re also referring to Philadelphia’s 30th Street Rail Station (that’s what ZFV stands for) as an airport. Do you think the conductor announces that train as “United flight 3174”? Man, I hope so.

- -

But the wacky train-instead-of-plane isn’t even the half of it. Take a look at the first half of this itinerary, and compare it to the original search. It’s the exact same flight!

- -

Comparison

- -

This type of airline pricing nonsense is not entirely uncommon. Opting for the lower fare and then getting off at Newark even has a name, ”Hidden city ticketing”. Still, it’s not very often that you can save over $200 just by missing a train.

]]>
-
- - - - - \ No newline at end of file diff --git a/RSParser/Tests/RSParserTests/Resources/ScriptingNews.json b/RSParser/Tests/RSParserTests/Resources/ScriptingNews.json deleted file mode 100644 index 2d658e00f..000000000 --- a/RSParser/Tests/RSParserTests/Resources/ScriptingNews.json +++ /dev/null @@ -1,945 +0,0 @@ -{ - "rss": { - "version": "2.0", - "xmlns:source": "http://source.scripting.com/", - "channel": { - "title": "Scripting News", - "link": "http://scripting.com/", - "description": "Scripting News, the weblog started in 1997 that bootstrapped the blogging revolution.", - "pubDate": "Mon, 26 Jun 2017 19:40:58 GMT", - "lastBuildDate": "Mon, 26 Jun 2017 19:41:48 GMT", - "language": "en-us", - "copyright": "© 1994-2017 Dave Winer.", - "generator": "oldSchool v0.42c", - "docs": "https://github.com/scripting/Scripting-News/blob/master/rss-in-json/README.md", - "source:localTime": "Mon, June 26, 2017 3:41 PM EDT", - "cloud": { - "domain": "rpc.rsscloud.io", - "port": 5337, - "path": "/pleaseNotify", - "registerProcedure": "", - "protocol": "http-post" - }, - "source:account": [ - { - "service": "twitter", - "#value": "davewiner" - }, - { - "service": "facebook", - "#value": "dave.winer.12" - }, - { - "service": "github", - "#value": "scripting" - }, - { - "service": "linkedin", - "#value": "scripting" - } - ], - "item": [ - { - "link": "http://scripting.com/2017/06/26.html#a080605", - "description": "Good morning students and teachers! 🍏 ", - "pubDate": "Mon, 26 Jun 2017 12:20:05 GMT", - "guid": "http://scripting.com/2017/06/26.html#a080605", - "source:outline": { - "text": "Good morning students and teachers! :green_apple: ", - "created": "Mon, 26 Jun 2017 12:20:05 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/26.html#a080605" - } - }, - { - "link": "http://scripting.com/2017/06/26.html#a030658", - "description": "This is the human side of Health care is socialist. ", - "pubDate": "Mon, 26 Jun 2017 19:40:58 GMT", - "guid": "http://scripting.com/2017/06/26.html#a030658", - "source:outline": { - "text": "This is the human side of Health care is socialist. ", - "created": "Mon, 26 Jun 2017 19:40:58 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/26.html#a030658" - } - }, - { - "link": "http://scripting.com/2017/06/26.html#a020604", - "description": "Interesting Politico piece posits that Trump acts as if he's mayor of the United States. If NYC is his model, that mayor is esp powerless, because the governor of the state also has a lot of power over the city. It's approx 1/2 of the population of the state, and probably much more than 1/2 of the money. For example, the MTA, which runs the buses and subway, is run by the state, not the city.", - "pubDate": "Mon, 26 Jun 2017 18:10:04 GMT", - "guid": "http://scripting.com/2017/06/26.html#a020604", - "source:outline": { - "text": "Interesting Politico piece posits that Trump acts as if he's mayor of the United States. If NYC is his model, that mayor is esp powerless, because the governor of the state also has a lot of power over the city. It's approx 1/2 of the population of the state, and probably much more than 1/2 of the money. For example, the MTA, which runs the buses and subway, is run by the state, not the city.", - "created": "Mon, 26 Jun 2017 18:10:04 GMT", - "type": "outline", - "image": "http://scripting.com/images/2017/06/26/quimby.png", - "permalink": "http://scripting.com/2017/06/26.html#a020604" - } - }, - { - "link": "http://scripting.com/2017/06/26.html#a020602", - "description": "Brent asks if the length in enclosures in RSS-in-JSON is a number or string. That's what the test podcast below is for. ", - "pubDate": "Mon, 26 Jun 2017 18:05:02 GMT", - "guid": "http://scripting.com/2017/06/26.html#a020602", - "source:outline": { - "text": "Brent asks if the length in enclosures in \"RSS-in-JSON\" is a number or string. That's what the test podcast below is for. ", - "created": "Mon, 26 Jun 2017 18:05:02 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/26.html#a020602" - } - }, - { - "link": "http://scripting.com/2017/06/26.html#a010633", - "description": "From time to time I have to do a podcast to test things out. This is one of those times. Let's see what happens. ", - "pubDate": "Mon, 26 Jun 2017 17:29:33 GMT", - "guid": "http://scripting.com/2017/06/26.html#a010633", - "enclosure": { - "url": "http://scripting.com/2017/06/26/yetAnotherTestPodcast.m4a", - "type": "audio/mpeg", - "length": 277413 - }, - "source:outline": { - "text": "From time to time I have to do a podcast to test things out. This is one of those times. Let's see what happens. ", - "created": "Mon, 26 Jun 2017 17:29:33 GMT", - "type": "outline", - "enclosure": "http://scripting.com/2017/06/26/yetAnotherTestPodcast.m4a", - "enclosureType": "audio/mpeg", - "enclosureLength": "277413", - "permalink": "http://scripting.com/2017/06/26.html#a010633" - } - }, - { - "link": "http://scripting.com/2017/06/26.html#a110603", - "description": "Body shaming is wrong no matter who you're using as the example. Someone is being hurt by this. No, I don't care how much you have suffered.", - "pubDate": "Mon, 26 Jun 2017 15:26:03 GMT", - "guid": "http://scripting.com/2017/06/26.html#a110603", - "source:outline": { - "text": "Body shaming is wrong no matter who you're using as the example. Someone is being hurt by this. No, I don't care how much you have suffered.", - "created": "Mon, 26 Jun 2017 15:26:03 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/26.html#a110603" - } - }, - { - "link": "http://scripting.com/2017/06/26.html#a090616", - "description": "I need an app to view RSS feeds in the browser because Chrome and Safari refuse to let me do that. I'd love to hear the reason why. ", - "pubDate": "Mon, 26 Jun 2017 13:24:16 GMT", - "guid": "http://scripting.com/2017/06/26.html#a090616", - "source:outline": { - "text": "I need an app to view RSS feeds in the browser because Chrome and Safari refuse to let me do that. I'd love to hear the reason why. ", - "created": "Mon, 26 Jun 2017 13:24:16 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/26.html#a090616" - } - }, - { - "title": "Subscribable feed lists give power to users", - "link": "http://scripting.com/2017/06/26.html#a080636", - "description": "

An interesting comment from Chris Aldrich about subscribing to lists of feeds in a thread on the Woodwind app site on GitHub.

\n

Here's the basic idea. There's a difference between importing OPML into a reader and subscribing to it. The latter is very powerful, for the user, but a lot of RSS reader devs may not want their users to have that much power. It's not a very hard feature to implement.

\n

The idea has been much-discussed here. We call them reading lists. Michael Arrington even wrote a TechCrunch piece about it in 2005.

\n

Subscribable OPML is something all my readers have been able to do through an OPML feature called inclusion. I wrote a howto for a River5 user re inclusion just last week.

\n

Share Your OPML was a service I operated for a while. It made it possible to manage your OPML separate from the reader you used. It was meant to encourage readers to support subscribable OPML. I'm looking for an excuse to bring it back, but first we need a base of shared feed lists.

\n

A lot of good stuff can be done if feed readers are willing to delegate list management to other services. IMHO the only reason a reader developer wouldn't do it is because they want to lock users in. If I let you edit your feed list elsewhere that means you could give the list to another vendor and have a choice which to use. It's really something users should demand, esp if you're paying for the service.

\n", - "pubDate": "Mon, 26 Jun 2017 12:24:36 GMT", - "guid": "http://scripting.com/2017/06/26.html#a080636", - "source:outline": { - "text": "Subscribable feed lists give power to users", - "created": "Mon, 26 Jun 2017 12:24:36 GMT", - "type": "outline", - "subs": [ - { - "text": "An interesting comment from Chris Aldrich about subscribing to lists of feeds in a thread on the Woodwind app site on GitHub. ", - "created": "Mon, 26 Jun 2017 12:24:49 GMT", - "image": "http://static.scripting.com/larryKing/images/2014/05/25/goodHumor.gif", - "permalink": "http://scripting.com/2017/06/26.html#a080649" - }, - { - "text": "Here's the basic idea. There's a difference between importing OPML into a reader and subscribing to it. The latter is very powerful, for the user, but a lot of RSS reader devs may not want their users to have that much power. It's not a very hard feature to implement. ", - "created": "Mon, 26 Jun 2017 12:40:32 GMT", - "permalink": "http://scripting.com/2017/06/26.html#a080632" - }, - { - "text": "The idea has been much-discussed here. We call them reading lists. Michael Arrington even wrote a TechCrunch piece about it in 2005. ", - "created": "Mon, 26 Jun 2017 12:26:01 GMT", - "permalink": "http://scripting.com/2017/06/26.html#a080601" - }, - { - "text": "Subscribable OPML is something all my readers have been able to do through an OPML feature called inclusion. I wrote a howto for a \"River5\" user re inclusion just last week.", - "created": "Mon, 26 Jun 2017 12:27:15 GMT", - "permalink": "http://scripting.com/2017/06/26.html#a080615" - }, - { - "text": "Share Your OPML was a service I operated for a while. It made it possible to manage your OPML separate from the reader you used. It was meant to encourage readers to support subscribable OPML. I'm looking for an excuse to bring it back, but first we need a base of shared feed lists. ", - "created": "Mon, 26 Jun 2017 12:33:10 GMT", - "permalink": "http://scripting.com/2017/06/26.html#a080610" - }, - { - "text": "A lot of good stuff can be done if feed readers are willing to delegate list management to other services. IMHO the only reason a reader developer wouldn't do it is because they want to lock users in. If I let you edit your feed list elsewhere that means you could give the list to another vendor and have a choice which to use. It's really something users should demand, esp if you're paying for the service. ", - "created": "Mon, 26 Jun 2017 12:47:13 GMT", - "permalink": "http://scripting.com/2017/06/26.html#a080613" - } - ], - "permalink": "http://scripting.com/2017/06/26.html#a080636" - } - }, - { - "link": "http://scripting.com/2017/06/25.html#a080631", - "description": "Good morning Internet guzzlers! 🍺", - "pubDate": "Sun, 25 Jun 2017 12:27:31 GMT", - "guid": "http://scripting.com/2017/06/25.html#a080631", - "source:outline": { - "text": "Good morning Internet guzzlers! :beer:", - "created": "Sun, 25 Jun 2017 12:27:31 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/25.html#a080631" - } - }, - { - "link": "http://scripting.com/2017/06/25.html#a080606", - "description": "So glad I stopped worrying about Facebook and am now blogging Old School style on scripting.com. I've found my sea legs once again. ", - "pubDate": "Sun, 25 Jun 2017 12:43:06 GMT", - "guid": "http://scripting.com/2017/06/25.html#a080606", - "source:outline": { - "text": "So glad I stopped worrying about Facebook and am now blogging Old School style on scripting.com. I've found my sea legs once again. ", - "created": "Sun, 25 Jun 2017 12:43:06 GMT", - "type": "tweet", - "tweetId": "878956761302147072", - "tweetUserName": "davewiner", - "permalink": "http://scripting.com/2017/06/25.html#a080606" - } - }, - { - "link": "http://scripting.com/2017/06/25.html#a080623", - "description": "Dan Shafer died. I knew Dan from the Mac developer community in the 80s, hired him to write the first docs for Frontier. Bon voyage mi amigo! 💥", - "pubDate": "Sun, 25 Jun 2017 12:56:23 GMT", - "guid": "http://scripting.com/2017/06/25.html#a080623", - "source:outline": { - "text": "Dan Shafer died. I knew Dan from the Mac developer community in the 80s, hired him to write the first docs for \"Frontier\". Bon voyage mi amigo! :boom:", - "created": "Sun, 25 Jun 2017 12:56:23 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/25.html#a080623" - } - }, - { - "link": "http://scripting.com/2017/06/25.html#a080624", - "description": "Money was a big issue yesterday in the nascent tech blogosphere. First, you do this because you love it, not because it pays well. (It doesn't pay at all.) Now I'd like to take you back to a discsussion that was had many years ago that resulted in this conclusion. You don't make money from this work, but it leads to opportunities where you can make money. Ideas and information make their way to you and if you are so-inclined you can make money by investing in those ideas. No sure things, but some bloggers have made billions, and others have made millions. And others have made a decent living. Not from their blog but because they blog. ", - "pubDate": "Sun, 25 Jun 2017 12:29:24 GMT", - "guid": "http://scripting.com/2017/06/25.html#a080624", - "source:outline": { - "text": "Money was a big issue yesterday in the nascent tech blogosphere. First, you do this because you love it, not because it pays well. (It doesn't pay at all.) Now I'd like to take you back to a discsussion that was had many years ago that resulted in this conclusion. You don't make money from this work, but it leads to opportunities where you can make money. Ideas and information make their way to you and if you are so-inclined you can make money by investing in those ideas. No sure things, but some bloggers have made billions, and others have made millions. And others have made a decent living. Not from their blog but because they blog. ", - "created": "Sun, 25 Jun 2017 12:29:24 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/25.html#a080624" - } - }, - { - "link": "http://scripting.com/2017/06/25.html#a080631", - "description": "In 2015 I wrote that Dropbox could be the king of the one-page app. Because storage is the thing the web doesn't, on its own, do, and storage is the thing Dropbox does best. And they have an API, and they understood the connection to one-page-apps earlier than anyone. But it didn't happen. I've emailed with people at Dropbox from time to time and the best explanation I can come up with is that they are focused in different areas. It seems to me, from my outside perch, that they are trying to become a competitor to Google's and Microsoft's Office products. I was hoping they'd become a platform, focusing on distribution and investment in startups.", - "pubDate": "Sun, 25 Jun 2017 12:32:31 GMT", - "guid": "http://scripting.com/2017/06/25.html#a080631", - "source:outline": { - "text": "In 2015 I wrote that Dropbox could be the king of the one-page app. Because storage is the thing the web doesn't, on its own, do, and storage is the thing Dropbox does best. And they have an API, and they understood the connection to one-page-apps earlier than anyone. But it didn't happen. I've emailed with people at Dropbox from time to time and the best explanation I can come up with is that they are focused in different areas. It seems to me, from my outside perch, that they are trying to become a competitor to Google's and Microsoft's Office products. I was hoping they'd become a platform, focusing on distribution and investment in startups.", - "created": "Sun, 25 Jun 2017 12:32:31 GMT", - "type": "outline", - "image": "http://radio3.io/icons/clarus.gif", - "permalink": "http://scripting.com/2017/06/25.html#a080631" - } - }, - { - "title": "Code mode is for real", - "link": "http://scripting.com/2017/06/25.html#a110613", - "description": "

\"Code

\n", - "pubDate": "Sun, 25 Jun 2017 15:53:13 GMT", - "guid": "http://scripting.com/2017/06/25.html#a110613", - "source:outline": { - "text": "Code mode is for real", - "created": "Sun, 25 Jun 2017 15:53:13 GMT", - "type": "outline", - "subs": [ - { - "text": "\"Code", - "created": "Sun, 25 Jun 2017 15:53:18 GMT", - "permalink": "http://scripting.com/2017/06/25.html#a110618" - } - ], - "permalink": "http://scripting.com/2017/06/25.html#a110613" - } - }, - { - "link": "http://scripting.com/2017/06/24.html#a100601", - "description": "Good morning sports fans! 🏈", - "pubDate": "Sat, 24 Jun 2017 14:57:01 GMT", - "guid": "http://scripting.com/2017/06/24.html#a100601", - "source:outline": { - "text": "Good morning sports fans! :football:", - "created": "Sat, 24 Jun 2017 14:57:01 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/24.html#a100601" - } - }, - { - "link": "http://scripting.com/2017/06/24.html#a040606", - "description": "Health care is socialist is getting a bunch of new reads today thanks to some powerful RTs.", - "pubDate": "Sat, 24 Jun 2017 20:50:06 GMT", - "guid": "http://scripting.com/2017/06/24.html#a040606", - "source:outline": { - "text": "Health care is socialist is getting a bunch of new reads today thanks to some powerful RTs.", - "created": "Sat, 24 Jun 2017 20:50:06 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/24.html#a040606" - } - }, - { - "link": "http://scripting.com/2017/06/24.html#a020644", - "description": "Today is emoji day ⭐️ on Scripting News. We have all the ⭐️ best emoji. And they're free, for you, the best ⭐️ people in the universe, the readers of ⭐️ this humble blog. ⭐️ ", - "pubDate": "Sat, 24 Jun 2017 18:34:44 GMT", - "guid": "http://scripting.com/2017/06/24.html#a020644", - "source:outline": { - "text": "Today is emoji day :star: on Scripting News. We have all the :star: best emoji. And they're free, for you, the best :star: people in the universe, the readers of :star: this humble blog. :star: ", - "created": "Sat, 24 Jun 2017 18:34:44 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/24.html#a020644" - } - }, - { - "link": "http://scripting.com/2017/06/24.html#a020620", - "description": "It makes sense that because of reconcilliation Repubs have a hard time repealing ObamaCare. It seems fair that you should have to have 60 votes to repeal something that required 60 votes to pass. How will they explain it to the voters they've been lying to about \"repeal and replace.\" Double-talk. Swamp-talk. 👍", - "pubDate": "Sat, 24 Jun 2017 18:20:20 GMT", - "guid": "http://scripting.com/2017/06/24.html#a020620", - "source:outline": { - "text": "It makes sense that because of reconcilliation Repubs have a hard time repealing ObamaCare. It seems fair that you should have to have 60 votes to repeal something that required 60 votes to pass. How will they explain it to the voters they've been lying to about \"repeal and replace.\" Double-talk. Swamp-talk. :+1:", - "created": "Sat, 24 Jun 2017 18:20:20 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/24.html#a020620" - } - }, - { - "link": "http://scripting.com/2017/06/24.html#a020649", - "description": "I've started a chatroom on Gitter. Not sure what I'll use it for. Also not sure if it's open for anyone to join. I want it to be. The community guidelines apply. Keep it short, respectful and on-topic, and no spam. ⚾️", - "pubDate": "Sat, 24 Jun 2017 18:05:49 GMT", - "guid": "http://scripting.com/2017/06/24.html#a020649", - "source:outline": { - "text": "I've started a chatroom on Gitter. Not sure what I'll use it for. Also not sure if it's open for anyone to join. I want it to be. The community guidelines apply. Keep it short, respectful and on-topic, and no spam. :baseball:", - "created": "Sat, 24 Jun 2017 18:05:49 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/24.html#a020649" - } - }, - { - "link": "http://scripting.com/2017/06/24.html#a110617", - "description": "BTW, don't ask me to pitch RSS, I won't do it, because the request is based on a misunderstanding. RSS is not a product, it's a format. I have never made a dime from it. You have as much at stake in its success as I do. So I always turn it around and ask the person who asked me for a pitch to instead pitch me on it. I don't budge on this. Ever. 🏀", - "pubDate": "Sat, 24 Jun 2017 15:19:17 GMT", - "guid": "http://scripting.com/2017/06/24.html#a110617", - "source:outline": { - "text": "BTW, don't ask me to pitch RSS, I won't do it, because the request is based on a misunderstanding. RSS is not a product, it's a format. I have never made a dime from it. You have as much at stake in its success as I do. So I always turn it around and ask the person who asked me for a pitch to instead pitch me on it. I don't budge on this. Ever. :basketball:", - "created": "Sat, 24 Jun 2017 15:19:17 GMT", - "type": "outline", - "image": "http://scripting.com/images/2017/06/24/rssTShirt.png", - "permalink": "http://scripting.com/2017/06/24.html#a110617" - } - }, - { - "link": "http://scripting.com/2017/06/24.html#a110610", - "description": "Is anyone using the JSON version of the Scripting News feed in their feed reader? It's a bit of a trick question, in a way, because as far as I know, only River5 and Electric River support this format. If so, send me an email at my address, on the About page here. 🍰", - "pubDate": "Sat, 24 Jun 2017 15:01:10 GMT", - "guid": "http://scripting.com/2017/06/24.html#a110610", - "source:outline": { - "text": "Is anyone using the JSON version of the Scripting News feed in their feed reader? It's a bit of a trick question, in a way, because as far as I know, only \"River5\" and \"Electric River\" support this format. If so, send me an email at my address, on the About page here. :cake:", - "created": "Sat, 24 Jun 2017 15:01:10 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/24.html#a110610" - } - }, - { - "link": "http://scripting.com/2017/06/24.html#a010650", - "description": "Journalists keep making a serious technology error -- assuming the only damage Russia can do to our government is during elections. 🇺🇸 ", - "pubDate": "Sat, 24 Jun 2017 05:50:50 GMT", - "guid": "http://scripting.com/2017/06/24.html#a010650", - "source:outline": { - "text": "Journalists keep making a serious technology error -- assuming the only damage Russia can do to our government is during elections. :us: ", - "created": "Sat, 24 Jun 2017 05:50:50 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/24.html#a010650" - } - }, - { - "link": "http://scripting.com/2017/06/24.html#a100632", - "description": "I get my health insurance through the ACA. It's very much alive, Spicer. Without it I would not have health insurance. 🍋 ", - "pubDate": "Sat, 24 Jun 2017 14:26:32 GMT", - "guid": "http://scripting.com/2017/06/24.html#a100632", - "source:outline": { - "text": "I get my health insurance through the ACA. It's very much alive, Spicer. Without it I would not have health insurance. :lemon: ", - "created": "Sat, 24 Jun 2017 14:26:32 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/24.html#a100632" - } - }, - { - "title": "The sad state of tech news in 2017", - "link": "http://scripting.com/2017/06/24.html#a090648", - "description": "

It won't take much to reboot the tech blogosphere, just a few bloggers with ideas who listen to each other and want to work with each other. That was the idea behind blogrolls, to visibly show the relationships.

\n
    \n\t
  • I'm still thinking about how to integrate a blogroll with the new design of Scripting News. I pushed everything aside to get a clean look for the new site. I want to avoid bringing it all back.
  • \n\t
\n

So, we have a few people who are writing and listening. That's a needed first step. Next we need a way to announce and hear about new tech products. Not just ones that get VC backing or come from big companies. We already hear about those products through TechMeme and the pubs that contribute to it. We also have platform-specific news about tech products, it's more limited, but it's there.

\n

It will likely start with word of mouth among the bloggers. If Richard is using a product and speaks highly of it, I'm likely to take a look. Especially if he says it fits into what I'm doing through the open formats my software already supports.

\n

Then I want a river, a place where I can go to find out quickly what's new, in the way of products, not BigCo bluster or another $250 million VC deal. I want to know what my peers are doing. So I can learn from them, and so we can make our products work with theirs.

\n

Work together is a phrase you'll hear me use a lot. It's the potential of tech, but it often isn't the attitude of tech. Even the smallest most independent developers dream of dominating. You can't work with people who dominate, even if they win.

\n

I want to hear about products that are open to connecting to mine.

\n

There have been times, often defined by news sources, that have created huge swells of compatible technology. To name a few: InfoWorld, PC WEEK, MacWEEK, TechCrunch. Very fond memories of the communities that gathered around each of those.

\n

It's time for another. The opportunity is there. It's been a long time since we had an open development community that worked to create great new user experience without lockin. It's like riding a bicycle or swimming, you don't forget how to do it. And like tennis or baseball, you can't play without partners and competition.

\n", - "pubDate": "Sat, 24 Jun 2017 13:43:48 GMT", - "guid": "http://scripting.com/2017/06/24.html#a090648", - "source:outline": { - "text": "The sad state of tech news in 2017", - "created": "Sat, 24 Jun 2017 13:43:48 GMT", - "type": "outline", - "subs": [ - { - "text": "It won't take much to reboot the tech blogosphere, just a few bloggers with ideas who listen to each other and want to work with each other. That was the idea behind blogrolls, to visibly show the relationships. ", - "created": "Sat, 24 Jun 2017 13:43:55 GMT", - "subs": [ - { - "text": "I'm still thinking about how to integrate a blogroll with the new design of \"Scripting News\". I pushed everything aside to get a clean look for the new site. I want to avoid bringing it all back. ", - "created": "Sat, 24 Jun 2017 14:32:17 GMT", - "permalink": "http://scripting.com/2017/06/24.html#a100617" - } - ], - "permalink": "http://scripting.com/2017/06/24.html#a090655" - }, - { - "text": "So, we have a few people who are writing and listening. That's a needed first step. Next we need a way to announce and hear about new tech products. Not just ones that get VC backing or come from big companies. We already hear about those products through \"TechMeme\" and the pubs that contribute to it. We also have platform-specific news about tech products, it's more limited, but it's there.", - "created": "Sat, 24 Jun 2017 13:44:09 GMT", - "permalink": "http://scripting.com/2017/06/24.html#a090609" - }, - { - "text": "It will likely start with word of mouth among the bloggers. If Richard is using a product and speaks highly of it, I'm likely to take a look. Especially if he says it fits into what I'm doing through the open formats my software already supports. ", - "created": "Sat, 24 Jun 2017 13:52:04 GMT", - "permalink": "http://scripting.com/2017/06/24.html#a090604" - }, - { - "text": "Then I want a river, a place where I can go to find out quickly what's new, in the way of products, not BigCo bluster or another $250 million VC deal. I want to know what my peers are doing. So I can learn from them, and so we can make our products work with theirs. ", - "created": "Sat, 24 Jun 2017 13:52:04 GMT", - "permalink": "http://scripting.com/2017/06/24.html#a090604" - }, - { - "text": "Work together is a phrase you'll hear me use a lot. It's the potential of tech, but it often isn't the attitude of tech. Even the smallest most independent developers dream of dominating. You can't work with people who dominate, even if they win. ", - "created": "Sat, 24 Jun 2017 13:54:33 GMT", - "permalink": "http://scripting.com/2017/06/24.html#a090633" - }, - { - "text": "I want to hear about products that are open to connecting to mine. ", - "created": "Sat, 24 Jun 2017 20:53:41 GMT", - "permalink": "http://scripting.com/2017/06/24.html#a040641" - }, - { - "text": "There have been times, often defined by news sources, that have created huge swells of compatible technology. To name a few: InfoWorld, PC WEEK, MacWEEK, TechCrunch. Very fond memories of the communities that gathered around each of those. ", - "created": "Sat, 24 Jun 2017 13:48:02 GMT", - "permalink": "http://scripting.com/2017/06/24.html#a090602" - }, - { - "text": "It's time for another. The opportunity is there. It's been a long time since we had an open development community that worked to create great new user experience without lockin. It's like riding a bicycle or swimming, you don't forget how to do it. And like tennis or baseball, you can't play without partners and competition. ", - "created": "Sat, 24 Jun 2017 13:57:02 GMT", - "permalink": "http://scripting.com/2017/06/24.html#a090602" - } - ], - "permalink": "http://scripting.com/2017/06/24.html#a090648" - } - }, - { - "title": "Republican-inspired art", - "link": "http://scripting.com/2017/06/24.html#a100632", - "description": "

\"A

\n", - "pubDate": "Sat, 24 Jun 2017 14:52:32 GMT", - "guid": "http://scripting.com/2017/06/24.html#a100632", - "source:outline": { - "text": "Republican-inspired art", - "created": "Sat, 24 Jun 2017 14:52:32 GMT", - "type": "outline", - "subs": [ - { - "text": "\"A", - "created": "Sat, 24 Jun 2017 14:52:37 GMT", - "permalink": "http://scripting.com/2017/06/24.html#a100637" - } - ], - "permalink": "http://scripting.com/2017/06/24.html#a100632" - } - }, - { - "link": "http://scripting.com/2017/06/23.html#a070648", - "description": "Thank you Om for the tweet-love. ❤️ ", - "pubDate": "Fri, 23 Jun 2017 11:50:48 GMT", - "guid": "http://scripting.com/2017/06/23.html#a070648", - "source:outline": { - "text": "Thank you Om for the tweet-love. :heart: ", - "created": "Fri, 23 Jun 2017 11:50:48 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/23.html#a070648" - } - }, - { - "link": "http://scripting.com/2017/06/23.html#a120634", - "description": "An idea worth RT'ing: \"A site people could go to, fill in some info about themselves, and find out how much they would lose under the Repub plan.\"", - "pubDate": "Fri, 23 Jun 2017 16:02:34 GMT", - "guid": "http://scripting.com/2017/06/23.html#a120634", - "source:outline": { - "text": "An idea worth RT'ing: \"A site people could go to, fill in some info about themselves, and find out how much they would lose under the Repub plan.\"", - "created": "Fri, 23 Jun 2017 16:02:34 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/23.html#a120634" - } - }, - { - "link": "http://scripting.com/2017/06/23.html#a090638", - "description": "githubpub is a Node app that serves from GitHub repositories.", - "pubDate": "Fri, 23 Jun 2017 13:52:38 GMT", - "guid": "http://scripting.com/2017/06/23.html#a090638", - "source:outline": { - "text": "\"githubpub\" is a Node app that serves from GitHub repositories.", - "created": "Fri, 23 Jun 2017 13:52:38 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/23.html#a090638" - } - }, - { - "link": "http://scripting.com/2017/06/23.html#a100602", - "description": "Fix for the Scripting News RSS feed: we now process glossary entries and emoji short codes. The net effect is that text shortcuts like RSS will be expanded as well as 👏 emoji 👏, in the feeds. As they say, still diggin!", - "pubDate": "Fri, 23 Jun 2017 14:52:02 GMT", - "guid": "http://scripting.com/2017/06/23.html#a100602", - "source:outline": { - "text": "Fix for the Scripting News RSS feed: we now process glossary entries and emoji short codes. The net effect is that text shortcuts like \"rss\" will be expanded as well as :clap: emoji :clap:, in the feeds. As they say, still diggin!", - "created": "Fri, 23 Jun 2017 14:52:02 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/23.html#a100602" - } - }, - { - "link": "http://scripting.com/2017/06/23.html#a070659", - "description": "So many thoughts today begin with -- If the Dems only had their shit together. ", - "pubDate": "Fri, 23 Jun 2017 11:52:59 GMT", - "guid": "http://scripting.com/2017/06/23.html#a070659", - "source:outline": { - "text": "So many thoughts today begin with -- If the Dems only had their shit together. ", - "created": "Fri, 23 Jun 2017 11:52:59 GMT", - "type": "tweet", - "tweetId": "878220762343133184", - "tweetUserName": "davewiner", - "permalink": "http://scripting.com/2017/06/23.html#a070659" - } - }, - { - "link": "http://scripting.com/2017/06/23.html#a070624", - "description": "If the Dems only had their shit together, we would be mobleizable to knock on neighbor's doors this weekend with pre-written talking points. \"Did you know that you could will lose your health insurance if the Republicans have their way?\"", - "pubDate": "Fri, 23 Jun 2017 11:53:24 GMT", - "guid": "http://scripting.com/2017/06/23.html#a070624", - "source:outline": { - "text": "If the Dems only had their shit together, we would be mobleizable to knock on neighbor's doors this weekend with pre-written talking points. \"Did you know that you could will lose your health insurance if the Republicans have their way?\"", - "created": "Fri, 23 Jun 2017 11:53:24 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/23.html#a070624" - } - }, - { - "link": "http://scripting.com/2017/06/23.html#a070633", - "description": "Repubs who consider voting for the repeal of Medicaid and the ACA should fear the hellfire they will face when they run for re-election. This weekend is the time to make your feelings felt. ", - "pubDate": "Fri, 23 Jun 2017 11:47:33 GMT", - "guid": "http://scripting.com/2017/06/23.html#a070633", - "source:outline": { - "text": "Repubs who consider voting for the repeal of Medicaid and the ACA should fear the hellfire they will face when they run for re-election. This weekend is the time to make your feelings felt. ", - "created": "Fri, 23 Jun 2017 11:47:33 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/23.html#a070633" - } - }, - { - "link": "http://scripting.com/2017/06/23.html#a070641", - "description": "BTW, if someone expresses frustration it doesn't follow that they blame you, esp on Twitter which is a length-impaired medium. ", - "pubDate": "Fri, 23 Jun 2017 11:45:41 GMT", - "guid": "http://scripting.com/2017/06/23.html#a070641", - "source:outline": { - "text": "BTW, if someone expresses frustration it doesn't follow that they blame you, esp on Twitter which is a length-impaired medium. ", - "created": "Fri, 23 Jun 2017 11:45:41 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/23.html#a070641" - } - }, - { - "title": "Thin servers", - "link": "http://scripting.com/2017/06/23.html#a100620", - "description": "

Two friends, Jon Udell and Mike Caulfield, are talking about \"dumb\" servers. I call the same things \"thin.\" Also fractional-horsepower servers. They go by a bunch of names, but the idea and motivation is the same.

\n

The idea: We move functionality from the server to the edge (desktop, mobile device), repeating until someday there's nothing left on the server. We could go all the way, but it needs a strong operational backend, something a big company is good at, not so much individuals. (With the caveat that some think this problem is distributable, notably the fictional CEO of Pied Piper.)

\n

The key thing is identity. Once you have that solved, it all becomes relatively easy. I've factored out identity into a layer I call nodeStorage. It associates storage with a user's Twitter identity. Twitter is a good service to use, unlike some others, because they have a liberal policy of who gets to create apps. Faceook has an extensive vetting process. Twitter is \"let a thousand flowers bloom.\" I know some people have problems with Twitter, but I've learned over many years that all corporate vendors are imperfect. If you're waiting for perfection you'll wait forever. And you build the software so that if Twitter should again become draconian, a new service can be filled in with as little disruption as possible.

\n

Another place I've looked is Dropbox. There, with one simple feature, the ability to associate a domain with a folder, they would solve the problem. I know there are external services that provide something \"like\" this, but fundamentally Dropbox doesn't provide enough flexibility in the API to do this in a reasonable way. (Lack of granularity in permissions, an app gets access to one folder or everything.)

\n

Or Amazon, if their identity system for AWS were simpler for end users, or if their end-user storage system could be accessed through the S3 API. I'm sure they've thought of it. There must be a reason they don't do it.

\n

And Twitter could completely eliminate the need for nodeStorage, by offering users a few gigabytes of storage attached to their Twitter account, accessible through the API. The first person who described the feature to me was Jack Dorsey, about eight years ago, when we met for coffee in SF. So he understands why this idea is so powerful. I'm not sure what the holdup is.

\n

In the meantime, nodeStorage works. I build the kind of apps I want. Open the sidebar on Scripting News (left margin) and have a look at the apps. Some even have source code so you can see for yourself.

\n", - "pubDate": "Fri, 23 Jun 2017 14:03:20 GMT", - "guid": "http://scripting.com/2017/06/23.html#a100620", - "source:outline": { - "text": "Thin servers", - "created": "Fri, 23 Jun 2017 14:03:20 GMT", - "type": "outline", - "subs": [ - { - "text": "Two friends, Jon Udell and Mike Caulfield, are talking about \"dumb\" servers. I call the same things \"thin.\" Also fractional-horsepower servers. They go by a bunch of names, but the idea and motivation is the same. ", - "created": "Fri, 23 Jun 2017 14:05:30 GMT", - "permalink": "http://scripting.com/2017/06/23.html#a100630" - }, - { - "text": "The idea: We move functionality from the server to the edge (desktop, mobile device), repeating until someday there's nothing left on the server. We could go all the way, but it needs a strong operational backend, something a big company is good at, not so much individuals. (With the caveat that some think this problem is distributable, notably the fictional CEO of Pied Piper.)", - "created": "Fri, 23 Jun 2017 14:24:35 GMT", - "permalink": "http://scripting.com/2017/06/23.html#a100635" - }, - { - "text": "The key thing is identity. Once you have that solved, it all becomes relatively easy. I've factored out identity into a layer I call \"nodeStorage\". It associates storage with a user's Twitter identity. Twitter is a good service to use, unlike some others, because they have a liberal policy of who gets to create apps. Faceook has an extensive vetting process. Twitter is \"let a thousand flowers bloom.\" I know some people have problems with Twitter, but I've learned over many years that all corporate vendors are imperfect. If you're waiting for perfection you'll wait forever. And you build the software so that if Twitter should again become draconian, a new service can be filled in with as little disruption as possible.", - "created": "Fri, 23 Jun 2017 14:05:43 GMT", - "image": "http://scripting.com/images/2017/06/17/bowling.png", - "permalink": "http://scripting.com/2017/06/23.html#a100643" - }, - { - "text": "Another place I've looked is Dropbox. There, with one simple feature, the ability to associate a domain with a folder, they would solve the problem. I know there are external services that provide something \"like\" this, but fundamentally Dropbox doesn't provide enough flexibility in the API to do this in a reasonable way. (Lack of granularity in permissions, an app gets access to one folder or everything.)", - "created": "Fri, 23 Jun 2017 14:08:41 GMT", - "permalink": "http://scripting.com/2017/06/23.html#a100641" - }, - { - "text": "Or Amazon, if their identity system for AWS were simpler for end users, or if their end-user storage system could be accessed through the S3 API. I'm sure they've thought of it. There must be a reason they don't do it. ", - "created": "Fri, 23 Jun 2017 14:09:10 GMT", - "permalink": "http://scripting.com/2017/06/23.html#a100610" - }, - { - "text": "And Twitter could completely eliminate the need for nodeStorage, by offering users a few gigabytes of storage attached to their Twitter account, accessible through the API. The first person who described the feature to me was Jack Dorsey, about eight years ago, when we met for coffee in SF. So he understands why this idea is so powerful. I'm not sure what the holdup is. ", - "created": "Fri, 23 Jun 2017 14:09:55 GMT", - "permalink": "http://scripting.com/2017/06/23.html#a100655" - }, - { - "text": "In the meantime, \"nodeStorage\" works. I build the kind of apps I want. Open the sidebar on Scripting News (left margin) and have a look at the apps. Some even have source code so you can see for yourself. ", - "created": "Fri, 23 Jun 2017 14:33:10 GMT", - "permalink": "http://scripting.com/2017/06/23.html#a100610" - } - ], - "permalink": "http://scripting.com/2017/06/23.html#a100620" - } - }, - { - "title": "RicMac, part II", - "link": "http://scripting.com/2017/06/23.html#a070643", - "description": "

Richard MacManus keeps on truckin. There's nothing more powerful than a persistent and curious user who's relatively fearless.

\n

In a follow-up post I learned that there is an IndieWeb-approved feed reader called Woodwind. That's good news. RSS and related technolgies, including OPML import and export, are essential components of the open web.

\n

BTW, to Richard, I wrote up my rules for standards-makers, based on experience re what (imho) is important and what works and doesn't. Another item for your consideration.

\n", - "pubDate": "Fri, 23 Jun 2017 11:14:43 GMT", - "guid": "http://scripting.com/2017/06/23.html#a070643", - "source:outline": { - "text": "RicMac, part II", - "created": "Fri, 23 Jun 2017 11:14:43 GMT", - "type": "outline", - "subs": [ - { - "text": "Richard MacManus keeps on truckin. There's nothing more powerful than a persistent and curious user who's relatively fearless. ", - "created": "Fri, 23 Jun 2017 11:20:38 GMT", - "image": "http://scripting.com/images/2017/06/23/mrNatural.png", - "permalink": "http://scripting.com/2017/06/23.html#a070638" - }, - { - "text": "In a follow-up post I learned that there is an IndieWeb-approved feed reader called Woodwind. That's good news. \"RSS\" and related technolgies, including OPML import and export, are essential components of the open web. ", - "created": "Fri, 23 Jun 2017 11:15:54 GMT", - "permalink": "http://scripting.com/2017/06/23.html#a070654" - }, - { - "text": "BTW, to Richard, I wrote up my rules for standards-makers, based on experience re what (imho) is important and what works and doesn't. Another item for your consideration. ", - "created": "Fri, 23 Jun 2017 11:19:38 GMT", - "permalink": "http://scripting.com/2017/06/23.html#a070638" - } - ], - "permalink": "http://scripting.com/2017/06/23.html#a070643" - } - }, - { - "title": "Test post", - "link": "http://scripting.com/2017/06/23.html#a030621", - "description": "

Here's a list with four items

\n
    \n\t
  • one
  • \n\t
  • two
  • \n\t
  • three
  • \n\t
  • four
  • \n\t
\n", - "pubDate": "Fri, 23 Jun 2017 19:48:21 GMT", - "guid": "http://scripting.com/2017/06/23.html#a030621", - "source:outline": { - "text": "Test post", - "created": "Fri, 23 Jun 2017 19:48:21 GMT", - "type": "outline", - "subs": [ - { - "text": "Here's a list with four items", - "created": "Fri, 23 Jun 2017 19:49:21 GMT", - "flNumberedSubs": "true", - "subs": [ - { - "text": "one", - "created": "Fri, 23 Jun 2017 19:48:43 GMT", - "permalink": "http://scripting.com/2017/06/23.html#a030643" - }, - { - "text": "two", - "created": "Fri, 23 Jun 2017 19:48:44 GMT", - "permalink": "http://scripting.com/2017/06/23.html#a030644" - }, - { - "text": "three", - "created": "Fri, 23 Jun 2017 19:48:45 GMT", - "permalink": "http://scripting.com/2017/06/23.html#a030645" - }, - { - "text": "four", - "created": "Fri, 23 Jun 2017 19:48:46 GMT", - "permalink": "http://scripting.com/2017/06/23.html#a030646" - } - ], - "permalink": "http://scripting.com/2017/06/23.html#a030621" - } - ], - "permalink": "http://scripting.com/2017/06/23.html#a030621" - } - }, - { - "link": "http://scripting.com/2017/06/22.html#a120633", - "description": "I'm working on a new Node web server that serves out of GitHub repos. It's a very sweet very small piece of software. ", - "pubDate": "Thu, 22 Jun 2017 16:28:33 GMT", - "guid": "http://scripting.com/2017/06/22.html#a120633", - "source:outline": { - "text": "I'm working on a new Node web server that serves out of GitHub repos. It's a very sweet very small piece of software. ", - "created": "Thu, 22 Jun 2017 16:28:33 GMT", - "type": "tweet", - "tweetId": "877926320822464512", - "tweetUserName": "davewiner", - "permalink": "http://scripting.com/2017/06/22.html#a120633" - } - }, - { - "link": "http://scripting.com/2017/06/22.html#a090616", - "description": "An epiphany. Mark Zuckerberg is his generation's Ray Kroc, and Facebook is McDonald's. I aspire to be Alice Waters.", - "pubDate": "Thu, 22 Jun 2017 13:29:16 GMT", - "guid": "http://scripting.com/2017/06/22.html#a090616", - "source:outline": { - "text": "An epiphany. Mark Zuckerberg is his generation's Ray Kroc, and Facebook is McDonald's. I aspire to be Alice Waters.", - "created": "Thu, 22 Jun 2017 13:29:16 GMT", - "type": "tweet", - "tweetId": "877881227298000896", - "tweetUserName": "davewiner", - "permalink": "http://scripting.com/2017/06/22.html#a090616" - } - }, - { - "link": "http://scripting.com/2017/06/22.html#a090644", - "description": "Future-of-journalism conferences that ignore blogging are not about the future of journalism.", - "pubDate": "Thu, 22 Jun 2017 13:29:44 GMT", - "guid": "http://scripting.com/2017/06/22.html#a090644", - "source:outline": { - "text": "Future-of-journalism conferences that ignore blogging are not about the future of journalism.", - "created": "Thu, 22 Jun 2017 13:29:44 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/22.html#a090644" - } - }, - { - "link": "http://scripting.com/2017/06/22.html#a120629", - "description": "News will be interesting tonight. They've got the Repub health care bill to rip apart, and it's also NBA Draft night. ", - "pubDate": "Thu, 22 Jun 2017 16:27:29 GMT", - "guid": "http://scripting.com/2017/06/22.html#a120629", - "source:outline": { - "text": "News will be interesting tonight. They've got the Repub health care bill to rip apart, and it's also NBA Draft night. ", - "created": "Thu, 22 Jun 2017 16:27:29 GMT", - "type": "tweet", - "tweetId": "877926057260728320", - "tweetUserName": "davewiner", - "permalink": "http://scripting.com/2017/06/22.html#a120629" - } - }, - { - "title": "Fargo puzzler", - "link": "http://scripting.com/2017/06/22.html#a030610", - "description": "

The last two episodes of season 3 of Fargo were fantastic. But, the opening scene of episode 1, which takes place in a police office in East Germany during the Cold War, is without explanation.

\n

All through the season, I was wondering how it was going to be connected up with the story that takes place in Minnesota in 2011, but as far as I know it never was.

\n

Maybe that was VM Varga as the accused? Or the police guy?

\n

This is kind of bothering me! :-)

\n

Okay then...

\n

Update: In the episode guide on Wikipedia they describe the opening scene as follows: \"In 1988 East Berlin, Jacob Ungerleider is questioned in the death of a woman, which he claims is a case of mistaken identity.\" So it's not VM Varga in the hot seat. Who is Jacob Ungerleider? I have no idea! ;-)

\n", - "pubDate": "Thu, 22 Jun 2017 19:26:10 GMT", - "guid": "http://scripting.com/2017/06/22.html#a030610", - "source:outline": { - "text": "Fargo puzzler", - "created": "Thu, 22 Jun 2017 19:26:10 GMT", - "type": "outline", - "subs": [ - { - "text": "The last two episodes of season 3 of Fargo were fantastic. But, the opening scene of episode 1, which takes place in a police office in East Germany during the Cold War, is without explanation. ", - "created": "Thu, 22 Jun 2017 19:26:18 GMT", - "image": "http://scripting.com/images/2017/06/22/paulBunyan.png", - "permalink": "http://scripting.com/2017/06/22.html#a030618" - }, - { - "text": "All through the season, I was wondering how it was going to be connected up with the story that takes place in Minnesota in 2011, but as far as I know it never was.", - "created": "Thu, 22 Jun 2017 19:27:20 GMT", - "permalink": "http://scripting.com/2017/06/22.html#a030620" - }, - { - "text": "Maybe that was VM Varga as the accused? Or the police guy?", - "created": "Thu, 22 Jun 2017 19:27:54 GMT", - "permalink": "http://scripting.com/2017/06/22.html#a030654" - }, - { - "text": "This is kind of bothering me! :-)", - "created": "Thu, 22 Jun 2017 19:28:15 GMT", - "permalink": "http://scripting.com/2017/06/22.html#a030615" - }, - { - "text": "Okay then...", - "created": "Thu, 22 Jun 2017 19:28:27 GMT", - "permalink": "http://scripting.com/2017/06/22.html#a030627" - }, - { - "text": "Update: In the episode guide on Wikipedia they describe the opening scene as follows: \"In 1988 East Berlin, Jacob Ungerleider is questioned in the death of a woman, which he claims is a case of mistaken identity.\" So it's not VM Varga in the hot seat. Who is Jacob Ungerleider? I have no idea! ;-)", - "created": "Thu, 22 Jun 2017 19:38:25 GMT", - "permalink": "http://scripting.com/2017/06/22.html#a030625" - } - ], - "permalink": "http://scripting.com/2017/06/22.html#a030610" - } - }, - { - "link": "http://scripting.com/2017/06/21.html#a060647", - "description": "Good morning summer solstice fans!", - "pubDate": "Wed, 21 Jun 2017 10:21:47 GMT", - "guid": "http://scripting.com/2017/06/21.html#a060647", - "source:outline": { - "text": "Good morning summer solstice fans!", - "created": "Wed, 21 Jun 2017 10:21:47 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/21.html#a060647" - } - }, - { - "link": "http://scripting.com/2017/06/21.html#a060611", - "description": "It was a boring NBA postseason, for the most. But the excitement of next season is already starting, with the draft tomorrow, and deal season in full swing. The place to find all the news is nbariver.com. It's one of many rivers maintained by my River5 installation. ", - "pubDate": "Wed, 21 Jun 2017 10:22:11 GMT", - "guid": "http://scripting.com/2017/06/21.html#a060611", - "source:outline": { - "text": "It was a boring NBA postseason, for the most. But the excitement of next season is already starting, with the draft tomorrow, and deal season in full swing. The place to find all the news is nbariver.com. It's one of many rivers maintained by my \"River5\" installation. ", - "created": "Wed, 21 Jun 2017 10:22:11 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/21.html#a060611" - } - }, - { - "link": "http://scripting.com/2017/06/21.html#a060632", - "description": "BTW, I hate the term \"eating the dogfood.\" As much as I love dogs, it says that our users are pets, not sentient human beings, our equals. It also says our software is dog food. I think as a kid, as an experiment, a few of us kids actually ate dog food. It's a vague memory, that must have some basis in reality. It makes me nauseous to think about it. And that's what I think about when I hear the term. Please, let's find another way of saying \"My software is good because I use it, and vice versa.\"", - "pubDate": "Wed, 21 Jun 2017 10:57:32 GMT", - "guid": "http://scripting.com/2017/06/21.html#a060632", - "source:outline": { - "text": "BTW, I hate the term \"eating the dogfood.\" As much as I love dogs, it says that our users are pets, not sentient human beings, our equals. It also says our software is dog food. I think as a kid, as an experiment, a few of us kids actually ate dog food. It's a vague memory, that must have some basis in reality. It makes me nauseous to think about it. And that's what I think about when I hear the term. Please, let's find another way of saying \"My software is good because I use it, and vice versa.\"", - "created": "Wed, 21 Jun 2017 10:57:32 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/21.html#a060632" - } - }, - { - "link": "http://scripting.com/2017/06/21.html#a070633", - "description": "Yesterday I posted a screen shot of one of my posts on Facebook, to accolades from friends on Facebook. I deleted the post. I won't be doing it again. Facebook is not a place for blog posts. Not as long as they disable linking, styles, titles and podcasts. If you want to help Facebook destroy the open web, go for it. But I will not participate in that awful adventure.", - "pubDate": "Wed, 21 Jun 2017 11:01:33 GMT", - "guid": "http://scripting.com/2017/06/21.html#a070633", - "source:outline": { - "text": "Yesterday I posted a screen shot of one of my posts on Facebook, to accolades from friends on Facebook. I deleted the post. I won't be doing it again. Facebook is not a place for blog posts. Not as long as they disable linking, styles, titles and podcasts. If you want to help Facebook destroy the open web, go for it. But I will not participate in that awful adventure.", - "created": "Wed, 21 Jun 2017 11:01:33 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/21.html#a070633" - } - }, - { - "link": "http://scripting.com/2017/06/21.html#a060600", - "description": "On Facebook you are who the algorithm says you are. ", - "pubDate": "Wed, 21 Jun 2017 10:53:00 GMT", - "guid": "http://scripting.com/2017/06/21.html#a060600", - "source:outline": { - "text": "On Facebook you are who the algorithm says you are. ", - "created": "Wed, 21 Jun 2017 10:53:00 GMT", - "type": "outline", - "permalink": "http://scripting.com/2017/06/21.html#a060600" - } - }, - { - "title": "GitHub API example app", - "link": "http://scripting.com/2017/06/21.html#a110614", - "description": "

A simple web app that travels through the River5 repository in my GitHub account, producing a directory that reflects the structure of the repo.

\n

I couldn't find sample code that does this simple thing. Now I won't have to hunt for it, and neither will you. ;-)

\n

Here's the source code.

\n", - "pubDate": "Wed, 21 Jun 2017 15:18:14 GMT", - "guid": "http://scripting.com/2017/06/21.html#a110614", - "source:outline": { - "text": "GitHub API example app", - "created": "Wed, 21 Jun 2017 15:18:14 GMT", - "type": "outline", - "subs": [ - { - "text": "A simple web app that travels through the River5 repository in my GitHub account, producing a directory that reflects the structure of the repo.", - "created": "Wed, 21 Jun 2017 15:18:24 GMT", - "permalink": "http://scripting.com/2017/06/21.html#a110624" - }, - { - "text": "I couldn't find sample code that does this simple thing. Now I won't have to hunt for it, and neither will you. ;-) ", - "created": "Wed, 21 Jun 2017 15:18:36 GMT", - "permalink": "http://scripting.com/2017/06/21.html#a110636" - }, - { - "text": "Here's the source code. ", - "created": "Wed, 21 Jun 2017 15:19:39 GMT", - "permalink": "http://scripting.com/2017/06/21.html#a110639" - } - ], - "permalink": "http://scripting.com/2017/06/21.html#a110614" - } - }, - { - "title": "Bike video from two years ago", - "link": "http://scripting.com/2017/06/21.html#a020637", - "description": "

\n", - "pubDate": "Wed, 21 Jun 2017 18:47:37 GMT", - "guid": "http://scripting.com/2017/06/21.html#a020637", - "source:outline": { - "text": "Bike video from two years ago", - "created": "Wed, 21 Jun 2017 18:47:37 GMT", - "type": "outline", - "subs": [ - { - "text": "", - "created": "Wed, 21 Jun 2017 18:47:50 GMT", - "permalink": "http://scripting.com/2017/06/21.html#a020650" - } - ], - "permalink": "http://scripting.com/2017/06/21.html#a020637" - } - }, - { - "title": "An old friend: Richard MacManus", - "link": "http://scripting.com/2017/06/21.html#a060651", - "description": "

Richard is one of the old school bloggers. He started ReadWriteWeb in 2003. It started as a Radio UserLand project and grew into a leading tech publication, something which I'm personally proud of.

\n

He has a new blog up and running. I've added it to my personal river here on Scripting News. He asks about where the blogrolls have gone, a topic I wrote about a couple of days ago. Richard would certainly be in my blogroll.

\n

Maybe the subscription list for my blogger's river would make a good start for my blogroll, or vice versa? Something we didn't do in the first iteration is make our rivers public. Nowadays I'm doing that routinely. A few examples are in the left sidebar here on Scripting News.

\n

Richard has turned to IndieWeb for the latest on open web tech. That's fine, but you have to look elsewhere too, because as he's discovered, they only embrace part of the open web. It's too bad they chose such an inclusive name, but have an exclusive approach. For example, they have avoided RSS, for reasons I'm sure I don't understand (I've listened, so no need to repeat the reasoning). We need all the advantages we can get because there are serious headwinds these days for blogging. RSS is serious open web technology. To not build on it is unthinkable, for me at least.

\n

Re integration between writing and reading, another topic of interest to Richard, all my rivers hook into Radio3, which is my latest linkblogging tool. For reading, I encouraged Richard to look at Electric River, it's the closest to what Radio UserLand did with aggregation in 2002. It runs on your Mac desktop, as the original did. When he wants to go all-in with rivers, nothing can take the place of River5, which is getting both modular and deep. I'm doing more work on that. Rivers have not finished evolving as far as I'm concerned.

\n", - "pubDate": "Wed, 21 Jun 2017 10:29:51 GMT", - "guid": "http://scripting.com/2017/06/21.html#a060651", - "source:outline": { - "text": "An old friend: Richard MacManus", - "created": "Wed, 21 Jun 2017 10:29:51 GMT", - "type": "outline", - "subs": [ - { - "text": "Richard is one of the old school bloggers. He started ReadWriteWeb in 2003. It started as a Radio UserLand project and grew into a leading tech publication, something which I'm personally proud of. ", - "created": "Wed, 21 Jun 2017 10:33:59 GMT", - "image": "http://scripting.com/2016/03/10/clown.png", - "permalink": "http://scripting.com/2017/06/21.html#a060659" - }, - { - "text": "He has a new blog up and running. I've added it to my personal river here on Scripting News. He asks about where the blogrolls have gone, a topic I wrote about a couple of days ago. Richard would certainly be in my blogroll.", - "created": "Wed, 21 Jun 2017 10:25:16 GMT", - "permalink": "http://scripting.com/2017/06/21.html#a060616" - }, - { - "text": "Maybe the subscription list for my blogger's river would make a good start for my blogroll, or vice versa? Something we didn't do in the first iteration is make our rivers public. Nowadays I'm doing that routinely. A few examples are in the left sidebar here on \"Scripting News\". ", - "created": "Wed, 21 Jun 2017 10:40:31 GMT", - "permalink": "http://scripting.com/2017/06/21.html#a060631" - }, - { - "text": "Richard has turned to IndieWeb for the latest on open web tech. That's fine, but you have to look elsewhere too, because as he's discovered, they only embrace part of the open web. It's too bad they chose such an inclusive name, but have an exclusive approach. For example, they have avoided \"RSS\", for reasons I'm sure I don't understand (I've listened, so no need to repeat the reasoning). We need all the advantages we can get because there are serious headwinds these days for blogging. RSS is serious open web technology. To not build on it is unthinkable, for me at least. ", - "created": "Wed, 21 Jun 2017 10:30:33 GMT", - "permalink": "http://scripting.com/2017/06/21.html#a060633" - }, - { - "text": "Re integration between writing and reading, another topic of interest to Richard, all my rivers hook into \"Radio3\", which is my latest linkblogging tool. For reading, I encouraged Richard to look at \"Electric River\", it's the closest to what Radio UserLand did with aggregation in 2002. It runs on your Mac desktop, as the original did. When he wants to go all-in with rivers, nothing can take the place of \"River5\", which is getting both modular and deep. I'm doing more work on that. Rivers have not finished evolving as far as I'm concerned. ", - "created": "Wed, 21 Jun 2017 10:32:27 GMT", - "permalink": "http://scripting.com/2017/06/21.html#a060627" - } - ], - "permalink": "http://scripting.com/2017/06/21.html#a060651" - } - }, - { - "link": "http://scripting.com/2017/06/20.html#a100647", - "description": "Anyone want to blog-debate about XML vs JSON? I've spent years using both, I think I have an objective view of the strengths of each. Imho, they are almost the same thing. XML has attributes and values, and that does make it more complex. Slightly. But you don't have to use the extra features. Look at OPML for an idea of a simple very JSON-like application of XML. Beyond that, there's really no difference. If you disagree, write a post, link to this and send me the link. I will read what you wrote, and respond, on my blog, if I have something to say. There's been so much bullshit flying around. I'd like to cut through that. ", - "pubDate": "Wed, 21 Jun 2017 02:56:47 GMT", - "guid": "http://scripting.com/2017/06/20.html#a100647", - "source:outline": { - "text": "Anyone want to blog-debate about XML vs JSON? I've spent years using both, I think I have an objective view of the strengths of each. Imho, they are almost the same thing. XML has attributes and values, and that does make it more complex. Slightly. But you don't have to use the extra features. Look at \"OPML\" for an idea of a simple very JSON-like application of XML. Beyond that, there's really no difference. If you disagree, write a post, link to this and send me the link. I will read what you wrote, and respond, on my blog, if I have something to say. There's been so much bullshit flying around. I'd like to cut through that. ", - "created": "Wed, 21 Jun 2017 02:56:47 GMT", - "type": "outline", - "image": "http://scripting.com/images/2017/06/20/penny.png", - "permalink": "http://scripting.com/2017/06/20.html#a100647" - } - } - ] - } - } -} \ No newline at end of file diff --git a/RSParser/Tests/RSParserTests/Resources/Subs.opml b/RSParser/Tests/RSParserTests/Resources/Subs.opml deleted file mode 100755 index b36fe4035..000000000 --- a/RSParser/Tests/RSParserTests/Resources/Subs.opml +++ /dev/null @@ -1,223 +0,0 @@ - - - - Subs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/RSParser/Tests/RSParserTests/Resources/SubsNoTitleAttributes.opml b/RSParser/Tests/RSParserTests/Resources/SubsNoTitleAttributes.opml deleted file mode 100755 index 55dd8ab4e..000000000 --- a/RSParser/Tests/RSParserTests/Resources/SubsNoTitleAttributes.opml +++ /dev/null @@ -1,237 +0,0 @@ - - - - Subs - - 0 - 472 - 502 - 1182 - 1252 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/RSParser/Tests/RSParserTests/Resources/YouTubeTheVolvoRocks.html b/RSParser/Tests/RSParserTests/Resources/YouTubeTheVolvoRocks.html deleted file mode 100644 index e158ef48e..000000000 --- a/RSParser/Tests/RSParserTests/Resources/YouTubeTheVolvoRocks.html +++ /dev/null @@ -1,24 +0,0 @@ -
AboutPressCopyrightContact usCreatorsAdvertiseDevelopersTermsPrivacyPolicy & SafetyHow YouTube worksTest new features
Randy Pobst - YouTube
\ No newline at end of file diff --git a/RSParser/Tests/RSParserTests/Resources/aktuality.rss b/RSParser/Tests/RSParserTests/Resources/aktuality.rss deleted file mode 100644 index 32986e75f..000000000 --- a/RSParser/Tests/RSParserTests/Resources/aktuality.rss +++ /dev/null @@ -1,710 +0,0 @@ - - - - Aktuality.sk - aktuálne spravodajstvo - https://www.aktuality.sk - Sun, 17 Jan 2021 23:46:22 +0100 - Denne aktualizované spravodajstvo z domova i zo sveta. Správy z oblasti ekonomiky, - kultúry a spoločenského života. Predpoveď počasia, horoskopy, TV program a kurzový lístok. - sk - Ringier Axel Springer SK - hourly - 1 - - - Analytici Goldman Sachs zvýšili odhad rastu ekonomiky USA - https://www.aktuality.sk/clanok/856577/analytici-goldman-sachs-zvysili-odhad-rastu-ekonomiky-usa/ - - https://t.aimg.sk/magaziny/vUw5YHzvQKDCkNw6PtXtAg.320.jpg?t=LzE1OHgxMDYvc21hcnQ&h=OcgUSVfIJOH9QPOW-nqQxQ&e=2145916800&v=3 - - <![CDATA[ - Analytici Goldman Sachs zvýšili odhad rastu ekonomiky USA - ]]> - - https://www.aktuality.sk/clanok/856577/analytici-goldman-sachs-zvysili-odhad-rastu-ekonomiky-usa/ - 150 - 150 - - - -
- Očakávajú totiž, že plán fiškálnych stimulov novozvoleného prezidenta Joea Bidena urýchli zotavovanie hospodárstva po pandémii ochorenia COVID-19. - ]]> -
- Sun, 17 Jan 2021 19:27:00 +0100 -
- - Koronavírus: Cielené testovanie zachytilo v Banskej Bystrici desiatky pozitívnych - https://www.aktuality.sk/clanok/856600/koronavirus-cielene-testovanie-zachytilo-v-banskej-bystrici-desiatky-pozitivnych/ - - https://t.aimg.sk/magaziny/uimnlYXjRubxkNz9qHHvKw.320~Pr-prava-odberov-v-Banskej-Bystrici.jpg?t=LzE1OHgxMDYvc21hcnQ&h=o5WAHblMYtS3LhTEGJqP5w&e=2145916800&v=2 - - <![CDATA[ - Koronavírus: Cielené testovanie zachytilo v Banskej Bystrici desiatky pozitívnych - ]]> - - https://www.aktuality.sk/clanok/856600/koronavirus-cielene-testovanie-zachytilo-v-banskej-bystrici-desiatky-pozitivnych/ - 150 - 150 - - - -
- V krajskom meste otestovali viac ako 8-tisíc ľudí, ktorí nemôžu pracovať z domu. - ]]> -
- Sun, 17 Jan 2021 19:25:00 +0100 -
- - Koronavírus: Slovensko čaká 9-dňové plošné testovanie, predĺžil sa zákaz vychádzania - https://www.aktuality.sk/clanok/856582/koronavirus-slovensko-caka-9-dnove-plosne-testovanie-predlzi-sa-aj-zakaz-vychadzania/ - - https://t.aimg.sk/magaziny/fXiDsLeeR2uP3wTcEQMcxg.320~Igor-Matovi-hovor-o-novom-pl-ne-pre-Slovensko-v-s-vislosti-s-koronav-rusom.jpg?t=LzE1OHgxMDYvc21hcnQ&h=UitS_uvS5fVTyL9_MCD3vw&e=2145916800&v=2 - - <![CDATA[ - Koronavírus: Slovensko čaká 9-dňové plošné testovanie, predĺžil sa zákaz vychádzania - ]]> - - https://www.aktuality.sk/clanok/856582/koronavirus-slovensko-caka-9-dnove-plosne-testovanie-predlzi-sa-aj-zakaz-vychadzania/ - 150 - 150 - - - -
- Celoslovenské plošné testovanie začne od zajtra a potrvá do 26. januára. Povinnosť mať certifikát o negatívnom teste na Covid-19 začne platiť od 27 januára. Otestovať sa budete môcť v mobilných odberných miestach. - ]]> -
- Sun, 17 Jan 2021 19:06:00 +0100 -
- - Komentár Ľubomíra Jaška: Pellegrini prerazil dno, ale Matovič mu v tom výrazne pomohol - https://www.aktuality.sk/clanok/856603/komentar-lubomira-jaska-pellegrini-prerazil-dno-ale-matovic-mu-v-tom-vyrazne-pomohol/ - - https://t.aimg.sk/magaziny/vjCQaQs5S-XhbgVKbM6O_A.320~Predseda-Hlasu-SD-Peter-Pellegrini.jpg?t=LzE1OHgxMDYvc21hcnQ&h=HbiWYr3kBM3gZZFg-66E8Q&e=2145916800&v=2 - - <![CDATA[ - Komentár Ľubomíra Jaška: Pellegrini prerazil dno, ale Matovič mu v tom výrazne pomohol - ]]> - - https://www.aktuality.sk/clanok/856603/komentar-lubomira-jaska-pellegrini-prerazil-dno-ale-matovic-mu-v-tom-vyrazne-pomohol/ - 150 - 150 - - - -
- Atómová bomba plošného testovania môže Pellegriniho zásluhou vybuchnúť úplne inak, ako si predstavuje najväčší fanúšik tyčiniek v nose. - ]]> -
- Sun, 17 Jan 2021 19:00:00 +0100 -
- - Británia vyzvala Čínu, aby umožnila inšpektorom z OSN navštíviť ujgurskú oblasť - https://www.aktuality.sk/clanok/856579/britania-vyzvala-cinu-aby-umoznila-inspektorom-z-osn-navstivit-ujgursku-oblast/ - - https://t.aimg.sk/magaziny/o7lvPB9UQa7nc2DDLRcIkg.320~Brit-nia-sa-dostala-na-zoznam-zelen-ch-kraj-n-Slov-kov-m-e-od-britskej-karant-ny-oslobodi-testovanie-na-letisku.jpg?t=LzE1OHgxMDYvc21hcnQ&h=AvL1vkAMoW9iJUmagwaVDg&e=2145916800&v=2 - - <![CDATA[ - Británia vyzvala Čínu, aby umožnila inšpektorom z OSN navštíviť ujgurskú oblasť - ]]> - - https://www.aktuality.sk/clanok/856579/britania-vyzvala-cinu-aby-umoznila-inspektorom-z-osn-navstivit-ujgursku-oblast/ - 150 - 150 - - - -
- Británia obvinila v utorok Čínu z porušovania ľudských práv ujgurskej menšiny a zaviedla obmedzenia pre dovoz tovaru, pri ktorom existuje podozrenie, že bol výsledkom nútenej práce. - ]]> -
- Sun, 17 Jan 2021 18:47:00 +0100 -
- - Guatemalská polícia použila slzotvorný plyn proti migrantom smerujúcim do USA - https://www.aktuality.sk/clanok/856584/guatemalska-policia-pouzila-slzotvorny-plyn-proti-migrantom-smerujucim-do-usa/ - - https://t.aimg.sk/magaziny/BWTlMtRQR9O3m3h1AVzYAQ.320~Guatemalsk-pol-cia-pou-ila-v-nede-u-slzotvorn-plyn-pri-pokuse-o-rozpt-lenie-tis-cov-migrantov-ktor-smeruj-do-Spojen-ch-t-tov.jpg?t=LzE1OHgxMDYvc21hcnQ&h=fgEYpogX5r_3yr9PP83DaQ&e=2145916800&v=2 - - <![CDATA[ - Guatemalská polícia použila slzotvorný plyn proti migrantom smerujúcim do USA - ]]> - - https://www.aktuality.sk/clanok/856584/guatemalska-policia-pouzila-slzotvorny-plyn-proti-migrantom-smerujucim-do-usa/ - 150 - 150 - - - -
- - ]]> -
- Sun, 17 Jan 2021 18:16:00 +0100 -
- - Lietadlo s Navaľným na palube pristálo v Moskve. Zadržali ho pri pasovej kontrole - https://www.aktuality.sk/clanok/856578/v-rade-cislo-13-a-v-obkluceni-novinarov-lietadlo-s-navalnym-na-palube-pristalo-v-moskve/ - - https://t.aimg.sk/magaziny/rQR7fDWdSUCrhoDxG5E-4w.320~Nava-nyj-v-obk-en-novin-rov-na-palube-lietadla-ruskej-spolo-nosti-Pobeda.jpg?t=LzE1OHgxMDYvc21hcnQ&h=ltlql5GozBU4I7ojtvlq_Q&e=2145916800&v=2 - - <![CDATA[ - Lietadlo s Navaľným na palube pristálo v Moskve. Zadržali ho pri pasovej kontrole - ]]> - - https://www.aktuality.sk/clanok/856578/v-rade-cislo-13-a-v-obkluceni-novinarov-lietadlo-s-navalnym-na-palube-pristalo-v-moskve/ - 150 - 150 - - - -
- Na moskovskom letisku Vnukovo, kde malo pristáť lietadlo s Navaľným na palube, poriadkové sily zadržiavali ľudí. Lietadlo nakoniec odklonili na letisko Šeremetievo. Ruského opozičníka zadržali pri pasovej kontrole. - ]]> -
- Sun, 17 Jan 2021 18:09:00 +0100 -
- - Zima ako u Mrázika. Na ruskú zimu neplatí ani vodka - https://www.aktuality.sk/clanok/850383/zima-ako-u-mrazika-na-rusku-zimu-neplati-ani-vodka/ - - https://t.aimg.sk/magaziny/ycvFEc7sRqDRN4gwgHe7RQ.320~Vyhod-vriacu-vodu-a-dopad-sneh-Ojmiakon-Rusko.jpg?t=LzE1OHgxMDYvc21hcnQ&h=EgmzER7E9H4nRn45dFNKMg&e=2145916800&v=2 - - <![CDATA[ - Zima ako u Mrázika. Na ruskú zimu neplatí ani vodka - ]]> - - https://www.aktuality.sk/clanok/850383/zima-ako-u-mrazika-na-rusku-zimu-neplati-ani-vodka/ - 150 - 150 - - - -
- Vedeli ste, že ani Rusi nezaháňajú zimu vodkou? Cestovateľ Martin Navrátil bol v meste Ojmiakon, kde bola nameraná najnižšia teplota na svete mimo Antarktídy a vypočul si to priamo od domácich. - ]]> -
- Sun, 17 Jan 2021 18:00:00 +0100 -
- - Bielorusko: V niekoľkých mestách pokračovali protesty proti Lukašenkovi - https://www.aktuality.sk/clanok/856576/bielorusko-v-niekolkych-mestach-pokracovali-protesty-proti-lukasenkovi/ - - https://t.aimg.sk/magaziny/0CMFQVwiS3-kV3J90Uv1gg.320~D-chodcovia-protestuj-proti-Alexandarovi-Luka-enkovi.jpg?t=LzB4MzozMjB4MTgzLzE1OHgxMDYvc21hcnQ&h=prxFwHNADbX61294ZAVPPg&e=2145916800&v=3 - - <![CDATA[ - Bielorusko: V niekoľkých mestách pokračovali protesty proti Lukašenkovi - ]]> - - https://www.aktuality.sk/clanok/856576/bielorusko-v-niekolkych-mestach-pokracovali-protesty-proti-lukasenkovi/ - 150 - 150 - - - -
- Denné demonštrácie sa konajú v Bielorusku od prezidentských volieb zo začiatku vlaňajšieho augusta. - ]]> -
- Sun, 17 Jan 2021 17:49:00 +0100 -
- - K Slovensku sa približuje arktický vzduch - https://www.aktuality.sk/clanok/856587/k-slovensku-sa-priblizuje-arkticky-vzduch/ - - https://t.aimg.sk/magaziny/p2tnVTC8Qxa26IHd5-AhPg.320~Ilustra-n-foto-J-no-kov-diery.jpg?t=LzE1OHgxMDYvc21hcnQ&h=7caByfMIXHR9QDBy1Lqk2g&e=2145916800&v=2 - - <![CDATA[ - K Slovensku sa približuje arktický vzduch - ]]> - - https://www.aktuality.sk/clanok/856587/k-slovensku-sa-priblizuje-arkticky-vzduch/ - 150 - 150 - - - -
- SHMÚ vydal na nedeľnú noc aj výstrahy pred nízkymi teplotami. Pri výstrahe prvého stupňa môžu teploty klesnúť až do mínus 17 stupňov Celzia. - ]]> -
- Sun, 17 Jan 2021 17:22:00 +0100 -
- - Vodička skončila so Škodou Felicia v Kunovskej priehrade - https://www.aktuality.sk/clanok/856575/policia-vodicka-skoncila-so-skodou-felicia-v-kunovskej-priehrade/ - - https://t.aimg.sk/magaziny/mEabrdgHQ5H-YvTISg66wg.320~Vodi-ka-skon-ila-so-kodou-Felicia-v-Kunovskej-priehrade.jpg?t=LzE1OHgxMDYvc21hcnQ&h=s85yNFKC_KFi3hT26TWlCw&e=2145916800&v=2 - - <![CDATA[ - Vodička skončila so Škodou Felicia v Kunovskej priehrade - ]]> - - https://www.aktuality.sk/clanok/856575/policia-vodicka-skoncila-so-skodou-felicia-v-kunovskej-priehrade/ - 150 - 150 - - - -
- Asi po dvoch hodinách sa jej podarilo z auta dostať a požiadala o pomoc okoloidúce auto. - ]]> -
- Sun, 17 Jan 2021 17:13:00 +0100 -
- - V čínskej bani, v ktorej zavalilo 22 baníkov, zrejme objavili známky života - https://www.aktuality.sk/clanok/856556/cina-v-bani-v-ktorej-zavalilo-22-banikov-zrejme-objavili-znamky-zivota/ - - https://t.aimg.sk/magaziny/P2BOOeGhSDO7oW2gT7Q_4g.320~V-buch-v-bani-v-meste-chi-sia-vo-v-chodo-nskej-provincii-an-tung.jpg?t=LzE1OHgxMDYvc21hcnQ&h=KmrXmPCrbOr5vJo0SSZa0w&e=2145916800&v=2 - - <![CDATA[ - V čínskej bani, v ktorej zavalilo 22 baníkov, zrejme objavili známky života - ]]> - - https://www.aktuality.sk/clanok/856556/cina-v-bani-v-ktorej-zavalilo-22-banikov-zrejme-objavili-znamky-zivota/ - 150 - 150 - - - -
- V bani v čase nehody prebiehali stavebné práce. - - - ]]> -
- Sun, 17 Jan 2021 16:49:00 +0100 -
- - Pri pobreží Turecka sa potopila nákladná loď s Ukrajincami a Rusmi, hlásia obete - https://www.aktuality.sk/clanok/856570/pri-pobrezi-turecka-sa-potopila-nakladna-lod-s-ukrajincami-a-rusmi-hlasia-obete/ - - https://t.aimg.sk/magaziny/H795qTJHRZaBIQjZoBwNyw.320~ilustra-n-foto.jpg?t=LzB4MjozMjB4MTgyLzE1OHgxMDYvc21hcnQ&h=evSZWlnugRGixpDhm7f-9A&e=2145916800&v=1 - - <![CDATA[ - Pri pobreží Turecka sa potopila nákladná loď s Ukrajincami a Rusmi, hlásia obete - ]]> - - https://www.aktuality.sk/clanok/856570/pri-pobrezi-turecka-sa-potopila-nakladna-lod-s-ukrajincami-a-rusmi-hlasia-obete/ - 150 - 150 - - - -
- Na palube bolo 12 členov posádky - dvaja Rusi a desať Ukrajincov, vyplýva z aktualizovaných údajov tureckého ministerstva dopravy. - ]]> -
- Sun, 17 Jan 2021 16:12:00 +0100 -
- - OĽANO: Z poslancov hnutia sa prednostne očkovať nebude nikto - https://www.aktuality.sk/clanok/856546/olano-z-poslancov-hnutia-sa-prednostne-ockovat-nebude-nikto/ - - https://t.aimg.sk/magaziny/Iir7W_-ZRHuRYKjFfGvQNw.320.jpg?t=LzE1OHgxMDYvc21hcnQ&h=tTKDjpcr-WCck0ii_K7hPw&e=2145916800&v=2 - - <![CDATA[ - OĽANO: Z poslancov hnutia sa prednostne očkovať nebude nikto - ]]> - - https://www.aktuality.sk/clanok/856546/olano-z-poslancov-hnutia-sa-prednostne-ockovat-nebude-nikto/ - 150 - 150 - - - -
- Ponuku predsedu NR SR Borisa Kollára na očkovanie organizované NR SR nevyužijú ani poslanci z klubu OĽANO nad 65 rokov či poslanci s chronickým ochorením, uviedlo hnutie. - ]]> -
- Sun, 17 Jan 2021 15:45:00 +0100 -
- - Ministerka Kolíková pripustila možné rozloženie plošného testovania na viac dní - https://www.aktuality.sk/clanok/856564/monitor-m-kolikova-pripustila-mozne-rozlozenie-plosneho-testovania-na-viac-dni/ - - https://t.aimg.sk/magaziny/oiH-LtzgSfKgzwjIo3P-xg.320~M-ria-Kol-kov.jpg?t=LzE1OHgxMDYvc21hcnQ&h=EmcC1VWiBAcwU58K_vHI4w&e=2145916800&v=2 - - <![CDATA[ - Ministerka Kolíková pripustila možné rozloženie plošného testovania na viac dní - ]]> - - https://www.aktuality.sk/clanok/856564/monitor-m-kolikova-pripustila-mozne-rozlozenie-plosneho-testovania-na-viac-dni/ - 150 - 150 - - - -
- Diskusia vo vláde sa podľa Kolíkovej týka aj toho, či treba viac sprísniť opatrenia a čo by to pre Slovensko znamenalo. - ]]> -
- Sun, 17 Jan 2021 15:13:00 +0100 -
- - Pellegrini pripúšťa referendovú kampaň pri testovaní, Kolíková hovorí o zneužívaní situácie - https://www.aktuality.sk/clanok/856555/monitor-pellegrini-pripusta-referendovu-kampan-pri-testovani-kolikova-namieta/ - - https://t.aimg.sk/magaziny/DFeuPDb7TKnoEHGyUmPxog.320.jpg?t=LzE1OHgxMDYvc21hcnQ&h=EwBwCDY2OSSoh2F18-MowQ&e=2145916800&v=2 - - <![CDATA[ - Pellegrini pripúšťa referendovú kampaň pri testovaní, Kolíková hovorí o zneužívaní situácie - ]]> - - https://www.aktuality.sk/clanok/856555/monitor-pellegrini-pripusta-referendovu-kampan-pri-testovani-kolikova-namieta/ - 150 - 150 - - - -
- Ministerka spravodlivosti Mária Kolíková (Za ľudí) reagovala, že ak to Pellegrini ako expremiér urobí, bude to hrubé podkopávanie dôvery v štát, čo v tejto situácii nikomu nepomôže - ]]> -
- Sun, 17 Jan 2021 14:46:00 +0100 -
- - Infektológ Krčméry: Viem si predstaviť, že by boli seniori zaočkovaní do Veľkej noci - https://www.aktuality.sk/clanok/856557/infektolog-krcmery-viem-si-predstavit-ze-by-boli-seniori-zaockovani-do-velkej-noci/ - - https://t.aimg.sk/magaziny/-bkXS45NRwvx3oDMN4lryw.320.jpg?t=LzB4MTU6MzIweDE5NS8xNTh4MTA2L3NtYXJ0&h=wQoXY1pwdk-Qnto_a4zB4Q&e=2145916800&v=5 - - <![CDATA[ - Infektológ Krčméry: Viem si predstaviť, že by boli seniori zaočkovaní do Veľkej noci - ]]> - - https://www.aktuality.sk/clanok/856557/infektolog-krcmery-viem-si-predstavit-ze-by-boli-seniori-zaockovani-do-velkej-noci/ - 150 - 150 - - - -
- Na margo toho, prečo sprísnenie opatrení neprišlo ešte pred Vianocami, Krčméry uviedol, že ústredný krízový štáb sa nevedel dohodnúť, či bude skôr plošné testovanie a potom lockdown, alebo naopak. - ]]> -
- Sun, 17 Jan 2021 14:36:00 +0100 -
- - Maroš Šefčovič: Vakcín bude dostatok na zaočkovanie vyše 80 % Európanov - https://www.aktuality.sk/clanok/856544/maros-sefcovic-vakcin-bude-dostatok-na-zaockovanie-vyse-80-europanov/ - - https://t.aimg.sk/magaziny/JxrtwXnyTF20fvkTGgU86g.320~Maro-ef-ovi.jpg?t=LzB4MzozMjB4MTgzLzE1OHgxMDYvc21hcnQ&h=0w-3LT932CbyFOGMzXGXDQ&e=2145916800&v=3 - - <![CDATA[ - Maroš Šefčovič: Vakcín bude dostatok na zaočkovanie vyše 80 % Európanov - ]]> - - https://www.aktuality.sk/clanok/856544/maros-sefcovic-vakcin-bude-dostatok-na-zaockovanie-vyse-80-europanov/ - 150 - 150 - - - -
- Na pôde ministerskej rady by sa malo v pondelok debatovať aj o tom, že by bolo dobré, keby všetky krajiny Únie minimálne dvakrát týždenne zverejňovali údaje o tom, koľko dostala ktorá krajina vakcín a koľko je v danom štáte zaočkovaných ľudí. - ]]> -
- Sun, 17 Jan 2021 14:12:00 +0100 -
- - Boris Kollár: Vakcinovať sa budú len poslanci v rizikovom veku a skupine - https://www.aktuality.sk/clanok/856533/monitor-b-kollar-vakcinovat-sa-budu-len-poslanci-v-rizikovom-veku-a-skupine/ - - https://t.aimg.sk/magaziny/Y824Wf-fRgHcRzr7rdsKkg.320.jpg?t=LzE1OHgxMDYvc21hcnQ&h=Ie6ihB3wClBg2c0UvZ-_-A&e=2145916800&v=2 - - <![CDATA[ - Boris Kollár: Vakcinovať sa budú len poslanci v rizikovom veku a skupine - ]]> - - https://www.aktuality.sk/clanok/856533/monitor-b-kollar-vakcinovat-sa-budu-len-poslanci-v-rizikovom-veku-a-skupine/ - 150 - 150 - - - -
- Zároveň uviedol, že hoci dostal výzvu na očkovanie poslancov, požiadal poslanecké kluby, aby možnosť využili poslanci v rizikovom veku či rizikovej skupine. - ]]> -
- Sun, 17 Jan 2021 13:43:00 +0100 -
- - Prieskum: Voľby by v januári vyhrala strana Hlas-SD, druhé je OĽANO - https://www.aktuality.sk/clanok/856542/prieskum-volby-by-v-januari-vyhrala-strana-hlas-sd-druhe-je-olano/ - - https://t.aimg.sk/magaziny/YBZLlaA5RUiWH1Xey7vgTw.320.jpg?t=LzE1OHgxMDYvc21hcnQ&h=_fZRRxFKwp3tQj23o_PX-w&e=2145916800&v=2 - - <![CDATA[ - Prieskum: Voľby by v januári vyhrala strana Hlas-SD, druhé je OĽANO - ]]> - - https://www.aktuality.sk/clanok/856542/prieskum-volby-by-v-januari-vyhrala-strana-hlas-sd-druhe-je-olano/ - 150 - 150 - - - -
- Podľa prieskumu by voliť na začiatku januára prišlo 49 percent ľudí, možnosť, že by skôr prišli voliť, označilo 18 percent opýtaných. - ]]> -
- Sun, 17 Jan 2021 13:14:00 +0100 -
- - V sudánskom regióne Dárfúr sa opäť rozhoreli násilnosti, hlásia desiatky obetí - https://www.aktuality.sk/clanok/856523/v-sudanskom-regione-darfur-sa-opat-rozhoreli-nasilnosti-hlasia-desiatky-obeti/ - - https://t.aimg.sk/magaziny/uA_I-efVQYGQoErTU3JSsA.320~Nepokoje-v-Sud-ne.jpg?t=LzE1OHgxMDYvc21hcnQ&h=uT7a6qvCQm9EkLuNU13bMw&e=2145916800&v=2 - - <![CDATA[ - V sudánskom regióne Dárfúr sa opäť rozhoreli násilnosti, hlásia desiatky obetí - ]]> - - https://www.aktuality.sk/clanok/856523/v-sudanskom-regione-darfur-sa-opat-rozhoreli-nasilnosti-hlasia-desiatky-obeti/ - 150 - 150 - - - -
- Krvavé udalosti, ktoré prebiehajú od soboty rána, zanechali aj 97 zranených. - ]]> -
- Sun, 17 Jan 2021 12:49:00 +0100 -
- - Prezidentka Čaputová o Matovičovom riadení krízy: Nevidím posun k lepšiemu - https://www.aktuality.sk/clanok/856538/prezidentka-caputova-o-matovicovom-riadeni-krizy-nevidim-posun-k-lepsiemu/ - - https://t.aimg.sk/magaziny/Er6pQuhATBTjzXx08cJn1Q.320~Prezidentka-Zuzana-ap-tov.jpg?t=LzE1OHgxMDYvc21hcnQ&h=I-C1vf5dqVT9MFPZjeJWXw&e=2145916800&v=2 - - <![CDATA[ - Prezidentka Čaputová o Matovičovom riadení krízy: Nevidím posun k lepšiemu - ]]> - - https://www.aktuality.sk/clanok/856538/prezidentka-caputova-o-matovicovom-riadeni-krizy-nevidim-posun-k-lepsiemu/ - 150 - 150 - - - -
- Podľa prezidentky je každý, kto má otázky k plošnému testovaniu, v súčasnosti označovaný ako triedny nepriateľ a sabotér. - ]]> -
- Sun, 17 Jan 2021 12:45:00 +0100 -
- - Kollár verí, že vláda prijme kroky, Pellegrini hovorí o telenovele - https://www.aktuality.sk/clanok/856527/monitor-b-kollar-veri-ze-vlada-prijme-kroky-p-pellegrini-hovori-o-telenovele/ - - https://t.aimg.sk/magaziny/ivwyvG2YT9HIpfqqYuuvew.320~Boris-Koll-r-o-vy-etrovan-smrti-Lu-ansk-ho-a-d-vere-tefanovi-Hol-mu.jpg?t=LzE1OHgxMDYvc21hcnQ&h=-_6e5uQd2es41cufI1CA5w&e=2145916800&v=5 - - <![CDATA[ - Kollár verí, že vláda prijme kroky, Pellegrini hovorí o telenovele - ]]> - - https://www.aktuality.sk/clanok/856527/monitor-b-kollar-veri-ze-vlada-prijme-kroky-p-pellegrini-hovori-o-telenovele/ - 150 - 150 - - - -
- Oponent v diskusii, nezaradený poslanec a líder strany Hlas Peter Pellegrini pripomenul, že vláda zatiaľ celý týždeň neúspešne rokuje o opatreniach a podobe plošného testovania. - ]]> -
- Sun, 17 Jan 2021 12:18:00 +0100 -
- - Koronavírus: Spresnili odkiaľ pochádzajú nové prípady - https://www.aktuality.sk/clanok/856515/koronavirus-spresnili-odkial-pochadzaju-nove-pripady/ - - https://t.aimg.sk/magaziny/mEOdTGA1TlWbm6imAFlPEw.320~Ilustra-n-foto.jpg?t=LzE1OHgxMDYvc21hcnQ&h=Hf45ewlfaxiXMQG7Ywv3dg&e=2145916800&v=2 - - <![CDATA[ - Koronavírus: Spresnili odkiaľ pochádzajú nové prípady - ]]> - - https://www.aktuality.sk/clanok/856515/koronavirus-spresnili-odkial-pochadzaju-nove-pripady/ - 150 - 150 - - - -
- Z ochorenia sa vyliečilo ďalších 2360 pacientov. - ]]> -
- Sun, 17 Jan 2021 12:09:00 +0100 -
- - Parkoviská na bratislavskej Kolibe či Kamzíku sú aj dnes plné - https://www.aktuality.sk/clanok/856513/parkoviska-na-bratislavskej-kolibe-ci-kamziku-su-aj-dnes-plne/ - - https://t.aimg.sk/magaziny/doYZGE76SR7ykX8pn3p9VQ.320~Policajti-reguluj-parkovanie-na-pr-jazdovej-ceste-na-Kolibu.jpg?t=LzE1OHgxMDYvc21hcnQ&h=P2lm47a3Id2SrLXP2GWuMQ&e=2145916800&v=2 - - <![CDATA[ - Parkoviská na bratislavskej Kolibe či Kamzíku sú aj dnes plné - ]]> - - https://www.aktuality.sk/clanok/856513/parkoviska-na-bratislavskej-kolibe-ci-kamziku-su-aj-dnes-plne/ - 150 - 150 - - - -
- Polícia vyzvala ľudí, aby využili na cestovanie mestskú kromadnú dopravu. - ]]> -
- Sun, 17 Jan 2021 12:04:00 +0100 -
- - Koronavírus: Záujem o testovanie je v Košiciach o polovicu nižší ako minule, výsledok príde aj ako sms - https://www.aktuality.sk/clanok/856519/koronavirus-zaujem-o-testovanie-je-v-kosiciach-o-polovicu-nizsi-ako-minule-vysledok-pride-aj-ako-sms/ - - https://t.aimg.sk/magaziny/RJuke6ltS8fy-BBNv29AxA.320~Otestova-sa-dobrovo-ne-m-u-aj-obyvatelia-Lun-ka-IX.jpg?t=LzE1OHgxMDYvc21hcnQ&h=8-YvSNilwUwKDCk8gM3mng&e=2145916800&v=2 - - <![CDATA[ - Koronavírus: Záujem o testovanie je v Košiciach o polovicu nižší ako minule, výsledok príde aj ako sms - ]]> - - https://www.aktuality.sk/clanok/856519/koronavirus-zaujem-o-testovanie-je-v-kosiciach-o-polovicu-nizsi-ako-minule-vysledok-pride-aj-ako-sms/ - 150 - 150 - - - -
- Výsledok z testovania prostredníctvom SMS správy príde Košičanom za necelú polhodinu. Záujem je o päťdesiat percent nižší, ako pred týždňom. - ]]> -
- Sun, 17 Jan 2021 11:55:00 +0100 -
- - Okresný súd Bratislava I vytýči termín s Liborom J. až v roku 2022 - https://www.aktuality.sk/clanok/856502/okresny-sud-bratislava-i-vytyci-termin-s-liborom-j-az-v-roku-2022/ - - https://t.aimg.sk/magaziny/6GfwoHLnQs2J6wPGmLowLA.320.jpg?t=LzE1OHgxMDYvc21hcnQ&h=G18EoWTPBkpKPgzUWPTOLA&e=2145916800&v=2 - - <![CDATA[ - Okresný súd Bratislava I vytýči termín s Liborom J. až v roku 2022 - ]]> - - https://www.aktuality.sk/clanok/856502/okresny-sud-bratislava-i-vytyci-termin-s-liborom-j-az-v-roku-2022/ - 150 - 150 - - - -
- Situáciu skomplikovalo, že doterajší sudca OS Bratislava I Roland Kemény je dlhodobo práceneschopný. - ]]> -
- Sun, 17 Jan 2021 11:41:00 +0100 -
- - V najvyšších polohách Tatier a Malej Fatre je zvýšené lavínové nebezpečenstvo - https://www.aktuality.sk/clanok/856510/v-najvyssich-polohach-tatier-a-malej-fatre-je-zvysene-lavinove-nebezpecenstvo/ - - https://t.aimg.sk/magaziny/EExpK-jPSxrOPHsafoJ5TA.320~Ilustra-n-sn-mka.jpg?t=LzE1OHgxMDYvc21hcnQ&h=ATWtH9xK3yzYtDkJzyfc8g&e=2145916800&v=2 - - <![CDATA[ - V najvyšších polohách Tatier a Malej Fatre je zvýšené lavínové nebezpečenstvo - ]]> - - https://www.aktuality.sk/clanok/856510/v-najvyssich-polohach-tatier-a-malej-fatre-je-zvysene-lavinove-nebezpecenstvo/ - 150 - 150 - - - -
- Vyššie stupne lavínového nebezpečenstva sú dôsledkom poslednej periódy sneženia, počas ktorej napadlo do 80 centimetrov nového snehu. Ten padal navyše za súčinnosti vetra. - ]]> -
- Sun, 17 Jan 2021 11:13:00 +0100 -
- - Ako znášate rádioaktívne žiarenie? Odpoveď máte možno v čreve - https://www.aktuality.sk/clanok/856293/ako-znasate-radioaktivne-ziarenie-odpoved-mate-mozno-v-creve/ - - https://t.aimg.sk/magaziny/BgJ1bO2HS2vmxHdvHCDkVQ.320~Ilustra-n-foto.jpg?t=LzB4MDozMjB4MTc5LzE1OHgxMDYvc21hcnQ&h=rRV1VDNSgKQ8ABz2O1APxQ&e=2145916800&v=1 - - <![CDATA[ - Ako znášate rádioaktívne žiarenie? Odpoveď máte možno v čreve - ]]> - - https://www.aktuality.sk/clanok/856293/ako-znasate-radioaktivne-ziarenie-odpoved-mate-mozno-v-creve/ - 150 - 150 - - - -
- Niektoré baktérie v čreve nás môžu chrániť aj pred rádioaktívnym žiarením, tvrdia vedci. - ]]> -
- Sun, 17 Jan 2021 11:00:00 +0100 -
- - Poslanci OĽANO navrhujú individuálne vzdelávanie umožniť i na druhom stupni - https://www.aktuality.sk/clanok/856497/nrsr-poslanci-olano-navrhuju-individualne-vzdelavanie-umoznit-i-na-druhom-stupni/ - - https://t.aimg.sk/magaziny/NsNzYd7LSPStEHj2hYMmvA.320~Ilustra-n-foto.jpg?t=LzB4MTA6MzIweDE5MC8xNTh4MTA2L3NtYXJ0&h=MTq5wFYGv2Dv22UBS0-jAg&e=2145916800&v=4 - - <![CDATA[ - Poslanci OĽANO navrhujú individuálne vzdelávanie umožniť i na druhom stupni - ]]> - - https://www.aktuality.sk/clanok/856497/nrsr-poslanci-olano-navrhuju-individualne-vzdelavanie-umoznit-i-na-druhom-stupni/ - 150 - 150 - - - -
- Cieľom návrhu zákona je vyriešenie doterajšieho stavu, v ktorom nie je umožnené individuálne vzdelávanie žiakov na druhom stupni základných škôl. - ]]> -
- Sun, 17 Jan 2021 10:45:00 +0100 -
-
-
\ No newline at end of file diff --git a/RSParser/Tests/RSParserTests/Resources/allthis-partial.json b/RSParser/Tests/RSParserTests/Resources/allthis-partial.json deleted file mode 100644 index 21b40e226..000000000 --- a/RSParser/Tests/RSParserTests/Resources/allthis-partial.json +++ /dev/null @@ -1 +0,0 @@ -{"description": "I just said what I said and it was wrong. Or was taken wrong.", "feed_url": "http://leancrew.com/all-this/feed.json", "title": "And now it’s all this", "items": [{"title": "Last thoughts on modifier keys", "url": "http://leancrew.com/all-this/2017/11/last-thoughts-on-modifier-keys/", "author": {"name": "Dr. Drang"}, "summary": "The first shall be last.", "date_published": "2017-11-23T21:08:29+00:00", "id": "http://leancrew.com/all-this/2017/11/last-thoughts-on-modifier-keys/", "content_html": "

When I wrote the post about ordering Mac modifier keys a few days ago, I was thinking primarily about the proper order of the symbols when writing about a keyboard shortcut, like ⌃⌥⌘P.1. I mentioned parenthetically that this order isn’t always observed when people speak about keyboard shortcuts or when they write the names of the keys out fully, as in “Command-Shift-3 takes a screenshot.”

\n

Jason Snell, in both a post at Six Colors and in conversation with John Siracusa on the lastest episode of Upgrade, took a stand against Apple’s ordering:2

\n
\n

Command is the commander! Command is the monarch of all keys! Command always comes first, in my book.

\n
\n

Siracusa agreed, and so do I. The ⌘ key is, and has always been, the key that signals a keyboard shortcut. While other modifier keys are sometimes used without ⌘—in cursor control and text selection, for example—I can’t think of any Apple applications that don’t use ⌘ to signal a keyboard shortcut for a menu item. And that primacy in shortcuts to menu items is, I think, why Apple puts it last rather than first.

\n

Keyboard shortcuts are always presented right-justified along the right edge of the menu. The most common shortcuts are just ⌘ and a letter, like ⌘N to start a new document, for example. It’s typically the variations on the basic command that get additional modifier keys, like ⌥⌘N to start a new project. If that were presented in a menu as ⌘⌥N, the menu would look wrong because the ⌘ symbols wouldn’t line up.

\n

Here’s the File menu in Safari:

\n

\"Safari

\n

There are two different New commands and three different Close commands. This, in Apple’s opinion (and mine), wouldn’t be right:

\n

\"Altered

\n

It’s not just having the ⌘ symbols aligned. The additional modifier symbols go in front because ⌘ is king and must sit next to the N or the W. The importance of the modifier decreases as you move away from the letter.

\n

It should go without saying—but I’ll say it anyway—that the letter (or number or whatever) key is the most important because nothing happens until it’s pressed.

\n

Having said all this, and despite agreeing with Apple’s symbol ordering, my ear for shortcut ordering works just like Jason’s and John’s. The main reason I use keyboard shortcut symbols in my posts instead of words is that I can read ⌥⇧⌘W and not be bothered because I don’t “hear” it as I read the symbols. “Option-Shift-Command-W,” on the other hand, gets sounded out in my head, and it sounds wrong.

\n

I suspect that’s why Apple’s own documentation sometimes gets the order wrong when the modifiers get written out as words. In speaking out the keys, “Command” is natural to put first because it announces that what’s coming is a keyboard shortcut.

\n
\n
\n
    \n
  1. \n

    Which happens to be the shortcut I use for previewing a blog post locally before publishing it. ↩︎

    \n
  2. \n
  3. \n

    In the original post, I said I didn’t know where the order was documented. A few people pointed me to both the Human Interface Guidelines and the Style Guide, where Apple gives the proper order explicitly. ↩︎

    \n
  4. \n
\n

\n

[If the formatting looks odd in your feed reader, visit the original article]

"}, {"title": "My next Mac", "url": "http://leancrew.com/all-this/2017/11/my-next-mac/", "author": {"name": "Dr. Drang"}, "summary": "Apple isn't making it easy to choose a Mac and hasn't for a few years.", "date_published": "2017-11-22T22:04:57+00:00", "id": "http://leancrew.com/all-this/2017/11/my-next-mac/", "content_html": "

Will probably be an iMac. I guess that spoils the suspense, doesn’t it?

\n

My iMac at work is the 27″ Late 2012 model, the one that came out one step before Retina came to the iMac. I don’t regret buying it, as my previous iMac (a 2006 model, I think) was absolutely on its last legs—constantly swapping to hard disk and running hot. I hadn’t meant to wait so long to replace it, but there was a long delay before that 2012 model came out, and I didn’t want to buy something that would be last year’s model almost as soon as I set it up.

\n

My home Mac is the venerable 2010 13″ MacBook Air, the first good Air. In the normal course of things, this would be the Mac I replace next, and I’ve been expecting to do so for a few years now. but…

\n

But Apple never came out with a Retina MacBook Air, choosing instead to go with the MacBook, which I find a little too far on the portable side of the portability/power spectrum. A couple of years ago, I had a crisis when my Air crapped out on me. It seemed wrong to put money into a five-year-old machine, but I wasn’t enthused about any of the MacBooks in the lineup at the time. I didn’t know the just-released 2015 MacBook Pro would turn out to be the best laptop ever made, I didn’t want to spend MacBook Pro money on my home/travel machine.

\n

The $280 logic board upgrade turned out to be a good investment, as I’m now 2½ years into my rejuvenated Air. Yes, it takes a while to wake up when I open the lid. Yes, its 128 GB SSD is tiny. No, it can’t take advantage of many of the iOS integration features that newer Macs can. And no, I don’t think it’s a good idea to install High Sierra on it. But it’s given me 30 months of faithful use, much more than I expected at the time.

\n

The announcement of the Touch Bar last year made me certain I’d be getting a MacBook Pro with it. A software-configurable set of controls seemed perfect for someone who’s always ginning up little scripts. But no one seems to like it, possibly because its configurability isn’t especially open to users. Bummer.

\n

I’ve delayed the decision on my home Mac for such a long time that now my office Mac is long in the tooth, too. Still working fine for most tasks, but just a Core 2 Duo machine that often makes me wait to scroll through long PDFs of scanned engineering and architectural drawings, something I need to do at work quite often. And no Retina.

\n

So it looks like my best bet is to buy a new iMac for work and bring my current office iMac home. This will put the power where I need it the most and will give me extra ooomph here at home. Especially with disk space (3 TB vs. 128 GB) and RAM (24GB vs. 4GB).

\n

It will be weird, though, as I haven’t had a desktop computer here at home in a dozen years. Will I enjoy being tethered to one spot in the house? And what about a travel computer?

\n

Both of these questions are made less pressing by the device I’m typing this on: a 9.7″ iPad Pro. While I agree with Gabe that it is by no means a Mac substitute, it can handle a lot of what I do at home and virtually everything I need to do on the road.1

\n

As for which iMac, I think I’ll settle on a middle-of-the-road 27″ configuration with a 3TB Fusion drive. Sort of the 2017 of what I bought in 2012.

\n
\n
\n
    \n
  1. \n

    I bought the iPad Pro last year as a sort of experiment to find out how comfortable I’d be working on it. I intend to write a full post about the results of that experiment soon, but in the meantime, you really should read Gabe’s post over at Macdrifter. I’ll probably use his post as a jumping-off point. And there may be a quiz. ↩︎

    \n
  2. \n
\n

\n

[If the formatting looks odd in your feed reader, visit the original article]

"}, {"title": "Modifier key order", "url": "http://leancrew.com/all-this/2017/11/modifier-key-order/", "author": {"name": "Dr. Drang"}, "summary": "Writing about Mac keyboard shortcuts? Make sure you put them in canonical order.", "date_published": "2017-11-20T02:22:59+00:00", "id": "http://leancrew.com/all-this/2017/11/modifier-key-order/", "content_html": "

If you write about Mac keyboard shortcuts, as I did yesterday, you should know how to do it right. Just as there’s a proper order for adjectives in English, there’s a proper order for listing the modifier keys in a shortcut.

\n

I haven’t found any documentation for this, but Apple’s preferred order is clear in how they show the modifiers in menus and how they’re displayed in the Keyboard Shortcuts Setting.

\n

\"Canonical

\n

The order is similar to how you see them down at the bottom left of your keyboard.

\n

\"Modifier

\n

Control (⌃), Option (⌥), and Command (⌘) always go in that order. The oddball is the Shift(⇧) key, which sneaks in just in front of Command.

\n

Keyboard Maestro recognizes this standard order and presents its “hot key” shortcut the same way.

\n

\"Keyboard

\n

(When people speak about keyboard shortcuts, it’s not uncommon to put Command first, e.g., “Command-Shift-3 takes a screenshot.” I’ve seen it written out that way, too. Apple is usually pretty careful to use the same order when using words as when using symbols. This page, for example, uses “Shift-Command-3,” to match the ⇧⌘3 you’d see in the Keyboard Shortcut Setti diff --git a/RSParser/Tests/RSParserTests/Resources/allthis.atom b/RSParser/Tests/RSParserTests/Resources/allthis.atom deleted file mode 100644 index 63262278a..000000000 --- a/RSParser/Tests/RSParserTests/Resources/allthis.atom +++ /dev/null @@ -1,520 +0,0 @@ - - - - And now it’s all this - - http://leancrew.com/all-this - I just said what I said and it was wrong. Or was taken wrong. - Thu, 23 Nov 2017 21:08:29 +0000 - en-US - hourly - 1 - http://wordpress.org/?v=4.0 - - - - -Last thoughts on modifier keys -http://leancrew.com/all-this/2017/11/last-thoughts-on-modifier-keys/ -Thu, 23 Nov 2017 21:08:29 +0000 - - - -http://leancrew.com/all-this/2017/11/last-thoughts-on-modifier-keys/ - - ordering Mac modifier keys a few days ago, I was thinking primarily about the proper order of the symbols when writing about a keyboard shortcut, like ⌃⌥⌘P.1. I mentioned parenthetically that this order isn’t always observed when people speak about keyboard shortcuts or when they write the names of the keys out fully, as in “Command-Shift-3 takes a screenshot.”]]> - - - When I wrote the post about ordering Mac modifier keys a few days ago, I was thinking primarily about the proper order of the symbols when writing about a keyboard shortcut, like ⌃⌥⌘P.1. I mentioned parenthetically that this order isn’t always observed when people speak about keyboard shortcuts or when they write the names of the keys out fully, as in “Command-Shift-3 takes a screenshot.”

-

Jason Snell, in both a post at Six Colors and in conversation with John Siracusa on the lastest episode of Upgrade, took a stand against Apple’s ordering:2

-
-

Command is the commander! Command is the monarch of all keys! Command always comes first, in my book.

-
-

Siracusa agreed, and so do I. The ⌘ key is, and has always been, the key that signals a keyboard shortcut. While other modifier keys are sometimes used without ⌘—in cursor control and text selection, for example—I can’t think of any Apple applications that don’t use ⌘ to signal a keyboard shortcut for a menu item. And that primacy in shortcuts to menu items is, I think, why Apple puts it last rather than first.

-

Keyboard shortcuts are always presented right-justified along the right edge of the menu. The most common shortcuts are just ⌘ and a letter, like ⌘N to start a new document, for example. It’s typically the variations on the basic command that get additional modifier keys, like ⌥⌘N to start a new project. If that were presented in a menu as ⌘⌥N, the menu would look wrong because the ⌘ symbols wouldn’t line up.

-

Here’s the File menu in Safari:

-

Safari File menu

-

There are two different New commands and three different Close commands. This, in Apple’s opinion (and mine), wouldn’t be right:

-

Altered Safari File menu

-

It’s not just having the ⌘ symbols aligned. The additional modifier symbols go in front because ⌘ is king and must sit next to the N or the W. The importance of the modifier decreases as you move away from the letter.

-

It should go without saying—but I’ll say it anyway—that the letter (or number or whatever) key is the most important because nothing happens until it’s pressed.

-

Having said all this, and despite agreeing with Apple’s symbol ordering, my ear for shortcut ordering works just like Jason’s and John’s. The main reason I use keyboard shortcut symbols in my posts instead of words is that I can read ⌥⇧⌘W and not be bothered because I don’t “hear” it as I read the symbols. “Option-Shift-Command-W,” on the other hand, gets sounded out in my head, and it sounds wrong.

-

I suspect that’s why Apple’s own documentation sometimes gets the order wrong when the modifiers get written out as words. In speaking out the keys, “Command” is natural to put first because it announces that what’s coming is a keyboard shortcut.

-
-
-
    -
  1. -

    Which happens to be the shortcut I use for previewing a blog post locally before publishing it. ↩︎

    -
  2. -
  3. -

    In the original post, I said I didn’t know where the order was documented. A few people pointed me to both the Human Interface Guidelines and the Style Guide, where Apple gives the proper order explicitly. ↩︎

    -
  4. -
-
-
-

[If the formatting looks odd in your feed reader, visit the original article]

]]> - -
- - -My next Mac -http://leancrew.com/all-this/2017/11/my-next-mac/ -Wed, 22 Nov 2017 22:04:57 +0000 - - - -http://leancrew.com/all-this/2017/11/my-next-mac/ - - - - - Will probably be an iMac. I guess that spoils the suspense, doesn’t it?

-

My iMac at work is the 27″ Late 2012 model, the one that came out one step before Retina came to the iMac. I don’t regret buying it, as my previous iMac (a 2006 model, I think) was absolutely on its last legs—constantly swapping to hard disk and running hot. I hadn’t meant to wait so long to replace it, but there was a long delay before that 2012 model came out, and I didn’t want to buy something that would be last year’s model almost as soon as I set it up.

-

My home Mac is the venerable 2010 13″ MacBook Air, the first good Air. In the normal course of things, this would be the Mac I replace next, and I’ve been expecting to do so for a few years now. but…

-

But Apple never came out with a Retina MacBook Air, choosing instead to go with the MacBook, which I find a little too far on the portable side of the portability/power spectrum. A couple of years ago, I had a crisis when my Air crapped out on me. It seemed wrong to put money into a five-year-old machine, but I wasn’t enthused about any of the MacBooks in the lineup at the time. I didn’t know the just-released 2015 MacBook Pro would turn out to be the best laptop ever made, I didn’t want to spend MacBook Pro money on my home/travel machine.

-

The $280 logic board upgrade turned out to be a good investment, as I’m now 2½ years into my rejuvenated Air. Yes, it takes a while to wake up when I open the lid. Yes, its 128 GB SSD is tiny. No, it can’t take advantage of many of the iOS integration features that newer Macs can. And no, I don’t think it’s a good idea to install High Sierra on it. But it’s given me 30 months of faithful use, much more than I expected at the time.

-

The announcement of the Touch Bar last year made me certain I’d be getting a MacBook Pro with it. A software-configurable set of controls seemed perfect for someone who’s always ginning up little scripts. But no one seems to like it, possibly because its configurability isn’t especially open to users. Bummer.

-

I’ve delayed the decision on my home Mac for such a long time that now my office Mac is long in the tooth, too. Still working fine for most tasks, but just a Core 2 Duo machine that often makes me wait to scroll through long PDFs of scanned engineering and architectural drawings, something I need to do at work quite often. And no Retina.

-

So it looks like my best bet is to buy a new iMac for work and bring my current office iMac home. This will put the power where I need it the most and will give me extra ooomph here at home. Especially with disk space (3 TB vs. 128 GB) and RAM (24GB vs. 4GB).

-

It will be weird, though, as I haven’t had a desktop computer here at home in a dozen years. Will I enjoy being tethered to one spot in the house? And what about a travel computer?

-

Both of these questions are made less pressing by the device I’m typing this on: a 9.7″ iPad Pro. While I agree with Gabe that it is by no means a Mac substitute, it can handle a lot of what I do at home and virtually everything I need to do on the road.1

-

As for which iMac, I think I’ll settle on a middle-of-the-road 27″ configuration with a 3TB Fusion drive. Sort of the 2017 of what I bought in 2012.

-
-
-
    -
  1. -

    I bought the iPad Pro last year as a sort of experiment to find out how comfortable I’d be working on it. I intend to write a full post about the results of that experiment soon, but in the meantime, you really should read Gabe’s post over at Macdrifter. I’ll probably use his post as a jumping-off point. And there may be a quiz. ↩︎

    -
  2. -
-
-
-

[If the formatting looks odd in your feed reader, visit the original article]

]]> -
-
- - -Modifier key order -http://leancrew.com/all-this/2017/11/modifier-key-order/ -Mon, 20 Nov 2017 02:22:59 +0000 - - - -http://leancrew.com/all-this/2017/11/modifier-key-order/ - - proper order for adjectives in English, there’s a proper order for listing the modifier keys in a shortcut.]]> - - - If you write about Mac keyboard shortcuts, as I did yesterday, you should know how to do it right. Just as there’s a proper order for adjectives in English, there’s a proper order for listing the modifier keys in a shortcut.

-

I haven’t found any documentation for this, but Apple’s preferred order is clear in how they show the modifiers in menus and how they’re displayed in the Keyboard Shortcuts Setting.

-

Canonical Mac modifier key order

-

The order is similar to how you see them down at the bottom left of your keyboard.

-

Modifier keys

-

Control (⌃), Option (⌥), and Command (⌘) always go in that order. The oddball is the Shift(⇧) key, which sneaks in just in front of Command.

-

Keyboard Maestro recognizes this standard order and presents its “hot key” shortcut the same way.

-

Keyboard Maestro hot key field

-

(When people speak about keyboard shortcuts, it’s not uncommon to put Command first, e.g., “Command-Shift-3 takes a screenshot.” I’ve seen it written out that way, too. Apple is usually pretty careful to use the same order when using words as when using symbols. This page, for example, uses “Shift-Command-3,” to match the ⇧⌘3 you’d see in the Keyboard Shortcut Settings. But even Apple slips up. On the grand Mac keyboard shortcut page, there are a few instances of “Command-Shift” instead of “Shift-Command.”)

-

The last bit of standard syntax is that the letter key in the shortcut (if there is a letter) is always presented as a capital, even when the Shift key isn’t used. I suspect this was a serious topic of discussion at Bandley 3 back in the early 80s as the Mac was being developed. They got it right. When entering a keyboard shortcut, you’re not typing a letter, you’re pressing a set of physical keys on the keyboard in front of you. The symbols on the letter keys are capitals, so that’s the appropriate way to identify those keys.

-
-

[If the formatting looks odd in your feed reader, visit the original article]

]]> -
-
- - -Command-E -http://leancrew.com/all-this/2017/11/command-e/ -Sun, 19 Nov 2017 02:02:50 +0000 - - - -http://leancrew.com/all-this/2017/11/command-e/ - - - - - Earlier this evening, Merlin tweeted out some advice we should all heed:

- - -

Even though I’ve been using a Mac for 32 years (albeit with an 8-year Linux hiatus) there were a few keyboard shortcuts in there I didn’t know about. And, just as important, several that I once knew but had fallen out of the habit of using.

-

But one of my favorites—and a big convenience when writing code or prose—isn’t on the Mac keyboard shortcut page. I wonder if the folks at Apple have forgotten it.

-

It has to do with finding text in a document. You know about ⌘F to specify a Find. That usually brings up a window or other control into which you can type (or paste) the text you’re going to be searching for. Often, there are options for controlling how the search will be performed and for replacement text. Here’s the Find window from BBEdit,

-

BBEdit Find window

-

here’s the Find window from Pages,

-

Pages Find window

-

and here’s TextEdit, which pops up a little Find section in the main window below the ruler,

-

TextEdit with Find section

-

You probably also know about ⌘G, which is usually given the menu name Find Next. It repeats the last Find command, allowing you to step through all the occurrences of that text in the document. There are usually other ways to cycle through all the found instances—Pages and TextEdit have arrow buttons you can click on—but ⌘G is the traditional way.1

-

A common situation, especially when looking through long documents or source code files, is to see a particular string of text and want to find other instances of it. You may, for example, see the definition of a function and want to search out where that function gets called later in the code. One way to do that would be to select the function name, copy it, ⌘F to bring up the text entry field, paste, and then ⌘G your way through the rest of the document.2 But the copy/⌘F/paste dance is a little clumsy.

-

Which is where ⌘E, which is typically given a menu name like Use Selection for Find, comes in.

-

BBEdit Search menu

-

Select the text you want to search for and hit ⌘E. That turns the selection into the search text, and you can go straight to ⌘G to walk through all the other occurrences.

-

I use ⌘E all the time when searching through code for function and variable names, as in the example above. It also works in Safari, Chrome, Preview, and PDF Expert, so I do similar quick searching on words I come across as I read news articles, blogs, reports, papers, etc.

-

⌘E works in other well-behaved applications, too, so you should give it a try. Note that “well-behaved applications” excludes MS Word. In Word, ⌘E centers the paragraph of text containing the selection. Of course.

-
-

Update Nov 19, 2017 8:40 AM
-A couple of things I forgot to include:

-

First, ⌘E in the Finder has, somewhat ironically, no relationship to finding. It ejects or unmounts the selected disk, assuming there is a selected disk. This shortcut is listed on Apple’s Mac shortcut page (which is why I was going to mention it), and a couple of people reminded me of it after the post was published. I guess I forgot about it because it’s a command I never use; I eject by right-clicking and selecting Eject from the menu that pops up.

-

Second, editors and word processors on iOS have not inherited this useful shortcut from the Mac. There is, of course, no ⌘ key on the software keyboard, but many of the traditional Mac shortcuts that use ⌘, ⌥, and ⇧ for moving the cursor and selecting text do work on iOS when you use an external keyboard. ⌘F and ⌘G (and its backward cousin, ⇧⌘G) often work in iOS, too, but ⌘E didn’t make the cut. Too bad.

-

Keyboard shortcuts in iOS Notes

-
-
-
-
    -
  1. -

    I believe ⌘G was originally used as the shortcut for Go To Page… in MacWrite, but it’s been Find Next for ages. 

    -
  2. -
  3. -

    Yes, source code editors in general, and BBEdit in particular, have other ways to find all the instances of a string of text, and those ways may be more appropriate in some situations. But stepping your way through each instance in turn is often the best way to see the string in full context. 

    -
  4. -
-
-
-

[If the formatting looks odd in your feed reader, visit the original article]

]]> -
-
- - -Converting fractions to decimal values -http://leancrew.com/all-this/2017/11/converting-fractions-to-decimal-values/ -Mon, 13 Nov 2017 04:35:39 +0000 - - - -http://leancrew.com/all-this/2017/11/converting-fractions-to-decimal-values/ - - - - - Recently, I’ve been getting Excel spreadsheets from clients that contain measurements in inches and/or fractions of an inch. In some cases, the cells with these measurements actually contain numbers but are displayed as fractions using one of the many formatting options Excel has.

-

Excel cell formatting options

-

In these cases, it’s easy for me to change the formatting to a standard decimal representation before exporting the sheet as a CSV file.

- -

Often, though, the measurements are entered as text. This is done by prefixing the data with a single quote mark, e.g.,

-
'2 5/16
-
-

or maybe

-
'2-5/16
-
-

These need to be converted into a decimal representation before exporting to CSV, so Pandas will recognize them as numbers rather than strings.1

-

To handle this situation, I created a Keyboard Maestro called Floatize. To use Floatize, I

-
    -
  • Select the column of data I want to convert.
  • -
  • Copy it to the clipboard.
  • -
  • Run Floatize, which converts the clipboard from a fractional representation to decimal.
  • -
  • Paste the result back into the column.
  • -
-

Here’s the macro:

-

Floatize Keyboard Maestro macro

-

It takes the clipboard, sends it to a script as standard input, and then puts the output back into the clipboard. Here’s the Python script it runs:

-
python:
- 1:  #!/usr/bin/env python
- 2:  
- 3:  from fractions import Fraction
- 4:  import re
- 5:  import sys
- 6:  
- 7:  def floatize(s):  
- 8:    if s in ['', 'n/a', 'none']:
- 9:      return ''
-10:    else:
-11:      try:
-12:        num = float(s)
-13:      except ValueError:
-14:        t = re.sub(r'(.*\d) *- *(\d.*)', r'\1 \2', s)
-15:        if t[0] == '-':
-16:          num = -float(sum(Fraction(x) for x in t[1:].split(None, 1)))
-17:        else:
-18:          num = float(sum(Fraction(x) for x in t.split(None, 1)))
-19:      return "{:f}".format(num)
-20:    
-21:  for line in sys.stdin.read().splitlines():
-22:    item = line.strip().lower()
-23:    print floatize(item)
-
-

The bulk of the fraction parsing is done by the fractions module in either Line 16 or Line 18. The rest of the script is an attempt to manipulate the oddball types of data I get into a format that fractions understands. Here’s what it can handle:

-
    -
  • Blank, n/a or none entries get turned into an empty string (Lines 8–9). Note that entries are converted to lower case in Line 22, before being passed to the floatize function, so this handles N/A and None, too.
  • -
  • Entries already in decimal format get converted to a float (Line 12) and then output in decimal format (Line 19). This is basically a pass-through.
  • -
  • Entries with a dash between the whole and fractional parts get the dash removed (Line 14).
  • -
  • Negative entries (Line 15) have their whole and fractional parts added together with the sign reversed (Line 16), and then output in decimal format (Line 19).
  • -
  • Positive entries have their whole and fractional parts added together (Line 18), and then output in decimal format (Line 19).
  • -
  • Entries that don’t look like any of the above cause an error.
  • -
-

The macro works perfectly in Numbers and BBEdit, but not in Excel. For some reason, nothing happens to the clipboard if I run the Floatize macro while Excel is the active application. I can, however, copy the column of fractions, activate another app, run Floatize, switch back to Excel and paste the converted clipboard. Another example of Excel not acting like a proper Mac app and another reason I move data out of Excel as quickly as I can.

-

Despite its inability to work inside Excel, this macro has been very handy over the past couple of weeks. I’ve used it to clean up several data sets that were loaded with fractions.

-

By the way, I will block anyone who tweets that I wouldn’t have this problem if I used the metric system.

-
-

Update Nov 13, 2017 10:04 PM  -The Twitter consensus is my problem in using this macro in Excel stems from Excel’s clipboard dumbassery. It apparently uses a private clipboard instead of the system clipboard until you bring another application to the front. Hence the macro’s inability to convert the clipboard unless I switched away from Excel.

-

Jimmy Hartington suggested this workaround:

-
-
-@drdrang Excel does something weird with the clipboard as you noticed.
Try this. Copy in Excel. Press Escape. Run the macro.
-By pressing Escape I think Excel stops messing with the clipboard. -
Jimmy Hartington (@jimmyhartington) Nov 12 2017 11:06 PM
-
-
-

This works perfectly and pointed me in the direction to make a better version of the macro, one that does all the copying, converting, and pasting:

-

New Floatize Keyboard Maestro macro

-

Note the conditional action that simulates the pressing of the Escape key only if Excel is the front application. This is necessary because Escape causes Numbers to deselect the column, which screws up the paste action at the end of the macro.

-

With this macro I can now select the column that needs converting and just press ⌃⌥⌘F to do the conversion in a single step. Thanks, Jimmy!

-
-
-
-
    -
  1. -

    There are probably ways to get Pandas to do the conversion as it imports the data, but I feel more comfortable—more in control—if I do it ahead of time. ↩︎

    -
  2. -
-
-
-

[If the formatting looks odd in your feed reader, visit the original article]

]]> -
-
- - -A modest proposal -http://leancrew.com/all-this/2017/11/a-modest-proposal/ -Tue, 07 Nov 2017 03:27:11 +0000 - - - -http://leancrew.com/all-this/2017/11/a-modest-proposal/ - - on my mind, I’d like to make a small complaint and suggestion to the folks at MacStories: some of your quarterly graphs could use a little scrubbing; they’d be much easier on the eyes if they were cleaned up a bit.]]> - - - With Apple sales and graphing on my mind, I’d like to make a small complaint and suggestion to the folks at MacStories: some of your quarterly graphs could use a little scrubbing; they’d be much easier on the eyes if they were cleaned up a bit.

-

I’m talking about the ones that look like this:

-

MacStories unit sales graph

-

My complaint is with the clutter along the x-axis. That uniform gray mass of text detracts from the data above it. And although putting a label at each quarter seems to provide more information to the reader, the reader actually gets less out of it because it’s too hard to read. The axis would be improved if it had just yearly Q1 labels. Maybe even one label every other year. Fewer labels would mean they could be turned back horizontal, which would be easier to read and would give more vertical space for the data.

-

I suspect every data point is labeled because that’s the way Numbers wants to do it, and MacStories’ automated system for generating these graphs follows the Numbers defaults. But if you were making these graphs by hand, you’d never label the x-axis this way. An automated system shouldn’t, either.

-

On the Mac, Numbers has a way to skip labels on the x-axis by selecting Custom Category Intervals from the Category Labels popup menu and then choosing how often to put the labels.

-

Mac Numbers axis options

-

Sadly, I can’t find that option in the iOS version. Maybe it’s tucked away in a less obvious place.

-

iOS Numbers axis options

-

Personally, I’m not a fan of either version of Numbers when it comes to making graphs because I like way more control than they give. But Numbers (like most graphing programs and libraries) can make nice graphs if you’re willing to break away from its defaults.

-

I don’t really want to pick on MacStories. Their graphs are as good as, if not better than, what you typically find in blogs and news sites. Most graphs, unfortunately, are based on the default settings of the software that made them, and come out looking a little clumsy.

-

I read MacStories because it has stylish writing. I’d like its graphs to be just as stylish.

-
-

Update Nov 7, 2017 8:55 PM
-I was informed on Twitter by two authoritative sources (this one and this one) that the MacStories graphs are made with Excel, not Numbers. I haven’t used Excel for graphing since the mid-90s, but even then it had more customization options than Numbers has now. So I’m crossing my fingers for cleaner x-axes in January’s graphs.

-
-
-

[If the formatting looks odd in your feed reader, visit the original article]

]]> -
-
- - -Apple sales graphs and the iPhone 7 -http://leancrew.com/all-this/2017/11/apple-sales-graphs-and-the-iphone-7/ -Mon, 06 Nov 2017 02:53:19 +0000 - - - -http://leancrew.com/all-this/2017/11/apple-sales-graphs-and-the-iphone-7/ - - sales and revenue figures, which means a boatload of graphs from all your fave rave Apple-oriented bloggers. I’m a few days late, as usual, despite having long ago written scripts to generate these graphs.]]> - - - Last week Apple released its 2017 Q4 (which everyone else’s calendar says is 2017 Q3) sales and revenue figures, which means a boatload of graphs from all your fave rave Apple-oriented bloggers. I’m a few days late, as usual, despite having long ago written scripts to generate these graphs.

-

I don’t make as many graphs as everyone else, partly because I’m lazy, but mainly because don’t care about revenue. My concern is the popularity of the devices I use, not how expensive they are. I want third-party developers to keep writing apps for them, and that means there has to be a market for them out there.

-

Here’s the summary graph, with the sales figures for the three main Apple products. The dots are the quarterly sales and the lines are the four-quarter moving averages.

-

Apple sales

-

The iPhone dominates the scale of this graphs, so it’s helpful to also see the iPad and Mac on their own graphs.

-

iPad sales

-

Mac sales

-

The good news is the second straight quarter of year-over-year increase in iPad sales after its long, well-documented slide since 2013. Additional good news is the Mac’s slow but consistent rise over the past year—four straight quarters of year-over-year sales increases. Imagine how much better the figures would be if people really liked the Touch Bar.

-

I think its fair to say, though, that the iPhone 7 was something of a dud. It had a good start, but its non-intro quarters had almost exactly the same sales as the 6S had. Look at the final three quarters of each.

-

Apple sales annotated

-

As I’ve argued before, the iPhone 6S sales looked lackluster only in comparison to the gangbusters popularity of the iPhone 6. If one ignored the year of the 6, the 6S’s sales were more or less on the same upward trend as the previous three editions. The iPhone 7 was on that same trend for its first quarter but went dead flat after that.

-

Apple’s report in January will be very interesting.

-
-

[If the formatting looks odd in your feed reader, visit the original article]

]]> -
-
- - -Another one-off Keyboard Maestro macro -http://leancrew.com/all-this/2017/11/another-one-off-keyboard-maestro-macro/ -Fri, 03 Nov 2017 02:02:41 +0000 - - - -http://leancrew.com/all-this/2017/11/another-one-off-keyboard-maestro-macro/ - - - - - Do you use Keyboard Maestro (or AppleScript or whatever) for one-time, throwaway macros as often as you should? I know I don’t, but I did put one together a couple of days ago and used a feature I’d never tried before.

-

I had one of those recalcitrant PDFs that I often get from clients. This one was 25–30 pages long, each an E-sized floor plan drawing for a building. The drawings were all black-and-white, but the PDF had color annotations added. I needed to add my own annotations to most of the pages, but something about the format of the file made it very cumbersome to work with. I tried Preview, PDF Expert, and PDfpen Pro, and they all were glacially slow when panning, zooming, and switching pages.

-

So I broke the file up into individual pages using PDFtk:

-
pdftk drawings.pdf burst
-
-

The single-page files didn’t make me wait for the spinning beach ball, so I was able to add my annotations quickly in PDF Expert. Then came an impasse.

-

I wanted to email the drawings with my annotations back to the client and to some other parties, but they were too big to fit in an email. I could use multiple emails, but that’s a recipe for losing some of the files. I could use a Dropbox link, but I had a sense that one of the other parties wouldn’t understand how that worked. What seemed best was to convert the files to JPEGs at the lowest legible resolution, zip them together, and send the zipped file in a single email.1

-

My normal practice would be to use sips for this, because I can issue a single command to convert any number of files. But I soon learned that sips doesn’t handle annotations properly when converting the format of a file from PDF to JPEG. In my brief testing, I found that neither my annotations or the ones that came from the client were visible in the converted JPEGs.

-

Preview, though, can export a PDF as a JPEG with the annotations intact and visible. Which presumably means that sips and Preview are using different code bases for the conversion. Whatever.

-

The problem with using Preview is I’d have to convert every file by hand. Not the most burdensome job I’ve ever had, but one that’s boring and susceptible to error. Enter Keyboard Maestro.

-

Here’s the macro that exports the current file in Preview to a JPEG and closes it:

-

Convert to JPEG macro

-

You’ll note there’s no step for setting the resolution for the exported file. That’s because once it’s set, it doesn’t change from one export to the next.

-

I didn’t try to have the macro open each PDF in turn, because I didn’t trust myself to do that right. I just opened all of them and then ran the macro by pressing ⌃⌥⌘J repeatedly. The macro closed each file after exporting it, leaving the next window ready to be operated on.

-

The new (for me) thing with this macro was the “and drag to” part at the bottom of the third step. That’s what moved the popup menu selection from PDF up to JPEG.

-

Preview export sheet

-

I figured out how much to drag by taking a screenshot like the one above and using a selection in Acorn to measure the vertical distance from the center of PDF to the center of JPEG. No trial and error.

-

With 25–30 files to convert, it’s possible I did save time with this macro. But the main reason I made it was to avoid the tedium and the likelihood of error on my part. These are not independent—I’m far more prone to make errors when the task is repetitive and doesn’t maintain my attention.

-
-
-
    -
  1. -

    No, a Dropbox link is really no more complicated than a zip file, but zip files are more familiar to more people. And although zipping JPEGs doesn’t make them smaller, it does package them up in a way that naive Windows users are usually comfortable with. It would be lovely if I always worked with people whose computer skills were trustworthy, but that’s not the world I live in. ↩︎

    -
  2. -
-
-
-

[If the formatting looks odd in your feed reader, visit the original article]

]]> -
-
- - -Binomial baseball -http://leancrew.com/all-this/2017/10/binomial-baseball/ -Mon, 30 Oct 2017 18:14:47 +0000 - - - -http://leancrew.com/all-this/2017/10/binomial-baseball/ - - - - - While reading a recap of last night’s World Series games, I saw this statistic: of the 65 Series that have had a sixth game, the team with the 3–2 lead has won the Series 43 times. This was, I think, intended to show us that the Astros have a strong chance to beat out the Dodgers for the title. And they do. But not as strong as you might expect.

-

If the teams were evenly balanced and each game independent of the others, we would expect the team with the 3–2 lead to win 75% of the time. 50% of the time they’d win the sixth game and the Series would be over; 25% of the time (50% of the other 50%) they’d win in the seventh game. So the leading team “should” have won the Series 48 or 49 times out of 65, not 43 times.

-

Is this 5 or 6 game difference meaningful? For that we need to do some calculations using the binomial distribution. Python’s SciPy set of libraries has a subsection of statistical modules, including one for binomial distribution calculations. We can import it this way:

-
python:
-from scipy.stats import binom
-
-

Let’s start by figuring out the probability that the leading team would win 43 times in 65 trials. With a 75% probability of winning the Series in each trial, the probability of 43 Series wins in 65 chances is calculated through

-
python:
-binom.pmf(43, 65, .75)
-
-

where the pmf function gets its name from the standard abbreviation for “probability mass function.” The answer is 0.029 or just under 3%. This makes it seem very unlikely that our assumption of 50–50 games would lead to only 43 Series wins for the leading team.

-

But that isn’t the way these sorts of calculations are normally done. If we want to find out if a seemingly out-of-whack result is “statistically significant,” we should look at the probability of results that are at least as far away from our expectations as the actual result was. In our case, that means looking not only at the probability of 43 Series wins out of 65 chances, but also 42 wins, 41 wins, and so on. We then add up all of these “at least as weird” probabilities.

-

The usual terminology for this sort of summation is “cumulative distribution function,” and the binom module has a function for it:

-
python:
-binom.cdf(43, 65, .75)
-
-

The result is 0.0695, or about 7%. Another way of looking at this is that if our assumption of 50–50 games were correct, there’s a 93% chance that the leading team would win the Series more than 43 times in 65 chances.

-

In hypothesis testing, the value 0.0695 is called the p-value, -and it’s common in many fields to consider a result statistically significant if its p-value is less than 0.05. Using that criterion, we would not take the difference between our “null hypothesis” of 50–50 games and the World Series history as statistically significant.1

-

But it’s something for Dodgers fans to cling to.

-
-
-
    -
  1. -

    Yes, I’ve been a little breezy here with my definition of null and alternate hypotheses and one-sided vs. two-sided rejection areas, but it’s just baseball. ↩︎

    -
  2. -
-
-
-

[If the formatting looks odd in your feed reader, visit the original article]

]]> -
-
- - -Judas -http://leancrew.com/all-this/2017/10/judas/ -Sat, 28 Oct 2017 14:33:47 +0000 - - - -http://leancrew.com/all-this/2017/10/judas/ - - Shadows in the Night and Fallen Angels, were strong clues that I was going to be seeing a Bob Dylan infected by the Great American Songbook.]]> - - - I can’t say I wasn’t warned. The concert’s promotional artwork (especially those fonts) and the previous two albums, Shadows in the Night and Fallen Angels, were strong clues that I was going to be seeing a Bob Dylan infected by the Great American Songbook.

-

Dylan concert promo

-

So yes, we got “Melancholy Mood” and “Autumn Leaves” and “The September of My Years” with Bob crooning into a big microphone and holding its stand at a jaunty angle. But that’s not what I disliked about the concert. In fact, those songs were done pretty well, and Dylan’s voice sounded better than any previous concert I’ve been to. It was what he did to his own songs that was frustrating.

-

No one in his right mind goes to a Dylan concert expecting to hear his classics played or sung the way they were done originally. But the treatment they’re given is always interesting and usually fun. Not this time. Apart from “It Ain’t Me, Babe” and “Highway 61 Revisited,” the Dylan songs suffered from arrangements that were, I guess, meant to match the style of the non-Dylan songs. Under these conditions, you can’t expect the songs to rock, but you can expect them to swing. And the band just didn’t swing.

-

There was a sameness to Charlie Sexton’s guitar work throughout the night. Despite changing instruments several times, his sound was constrained and repetitive. But when a band doesn’t swing, it’s mainly because of the rhythm section. These are talented musicians who can play in any style, so if they didn’t find a groove it must have been because Bob didn’t want them to.

-

As a result, he lost the audience. Many left early and those who hung in there stayed glued to their seats until the encores. Can you imagine a Dylan audience not standing to “Tangled Up in Blue”?

-

The optimist in me notes that Dylan never sticks with anything for long, and after two albums of standards he may be ready for the next thing. I hope he comes back to the notion that that it’s his songs, and the blues and country/folk they came out of, that are truly the Great American Songbook and gives them the treatment they deserve.

-
-

[If the formatting looks odd in your feed reader, visit the original article]

]]> -
-
- - -Icons -http://leancrew.com/all-this/2017/10/icons/ -Fri, 27 Oct 2017 01:39:06 +0000 - - - -http://leancrew.com/all-this/2017/10/icons/ - - Brett Terpstra’s iTunesIcon Automator app, which just got an update.]]> - - - I have some code sitting around here somewhere for extracting the images (app icons, book covers, album art, etc.) from the various iTunes and App Stores. But I think it’ll be easier to use Brett Terpstra’s iTunesIcon Automator app, which just got an update.

-

Apart from its appeal to my laziness, Brett’s little app has a cool icon that I took an instant liking to.

-

iTunesIcon icon

-

If you work around industrial and construction equipment, Brett’s icon should bring a smile of familiarity. It’s highly reminiscent of the hands in safety signs and decals that are in dire straits.

-

Safety symbols

-

Images from SafetySign.com

-

I’m sure your fingers are safe when using iTunesIcon.

-
-

[If the formatting looks odd in your feed reader, visit the original article]

]]> -
-
- - -Feed reading -http://leancrew.com/all-this/2017/10/feed-reading/ -Mon, 23 Oct 2017 00:10:42 +0000 - - - -http://leancrew.com/all-this/2017/10/feed-reading/ - - a nice article today on how he uses Feedbin to handle his reading on the web. If you follow Gabe—and if not, why don’t you?—it will not surprise you to learn that he’s formidably organized. I don’t see myself following in his footsteps, but it’s always useful to learn how smart people do things. His article also reminded me that I’ve been meaning to write about my feed reading setup.]]> - - - Gabe Weatherhead has a nice article today on how he uses Feedbin to handle his reading on the web. If you follow Gabe—and if not, why don’t you?—it will not surprise you to learn that he’s formidably organized. I don’t see myself following in his footsteps, but it’s always useful to learn how smart people do things. His article also reminded me that I’ve been meaning to write about my feed reading setup.

-

A couple of years ago, I let my subscription to Feed Wrangler lapse and started using a homemade, web-based RSS reading system. The heart of the system is still the script described in this post, but with a some changes as I thought of better ways of doing things.

-

The biggest change came in the past few months. Initially, I created a single static web page with all of today’s articles from the feeds I subscribe to. A cron task updated the page a few times an hour throughout the day. In this system, “today” was defined as “from 10:00 pm last night until now,” and the page would grow in size from morning to night.

-

The advantage of this temporal arrangement from a programming point of view was that I didn’t have to write any code to keep track of whether I’d already read an article or not, and there were no external dependencies. If it was published “today,” it got on the page.

-

The disadvantage was from the reading point of view. As I visited the page throughout the day, it became more and more filled with article I’d already read. This wasn’t as terrible as you might think. The articles were arranged in reverse chronological order by publication time, so the ones I’d read were typically at the bottom of the page. I say “typically” because some feeds—XKCD comes to mind—are very bad at providing accurate publication times and their articles would sometimes end up at the bottom despite being recently published.

-

Eliminating the reading disadvantage meant keeping track of what I’d read and showing only what I hadn’t—eliminating the programming advantage. I decided to keep track of read articles in an SQLite database and to add items to that database through a button placed at the bottom of each article on my RSS page.

-

RSS buttons

-

This meant

-
    -
  1. Building a database that would uniquely identify every article. This was pretty simple. Each record has just two fields: the name of the website and the unique article ID (which is often just the article’s URL but is sometimes a long alphanumeric string generated by the site’s blogging software).
  2. -
  3. Altering the existing script that builds the RSS page to filter out feed items that are in the database. Because Python has an SQLite module as part of its standard library and the syntax of SQL commands is straightforward, this wasn’t as tricky as I thought it would be. In fact, the new code is easier to read than the time-based filtering code I removed.
  4. -
  5. Writing a server script (basically just a CGI script) to add an article to the database when given the blog name and article ID via the POST method. It’s been a while since I last wrote a CGI script, but it was like riding a bicycle.
  6. -
  7. Adding some JavaScript with XMLHttpRequest to the RSS page to call the server script when a button is pressed. This took the most time, mainly because everyone in the world (except me) knows how to do AJAX now, and finding references written at an appropriately low level was harder than I expected. I found this Stack Overflow discussion helpful.
  8. -
-

So now I usually tap the Mark as read button when I get to the end of an article. If it’s a long article that I want to read later, I don’t mark it as read, and it’ll be there the next time I bring up the RSS page.

-

Fearing I’d forget how to use XMLHttpRequest, I quickly included another form at the end of each article for adding that article to my Pinboard account. I didn’t bother adding labels to the text field, because I’m the only one who uses this and I know the field is for tags. I did, however, include some DOM stuff to make it obvious when I’d marked an article as read or added it to Pinboard.

-

RSS buttons marked

-

What I like about this system is how portable and (I hope) future-proof it is. I’ve been reluctant to sign on with Feedbin or Feedly or BazQux or any of the other Google Reader replacements because I worry they’ll write a Medium post and disappear with my subscription list and whatever organization scheme I’ve created. My system can run on any web server with Python, SQLite, and a cgi-bin directory. I think that’ll mean “any server, anywhere” for a very long time.

-
-

[If the formatting looks odd in your feed reader, visit the original article]

]]> -
-
- -
-
- diff --git a/RSParser/Tests/RSParserTests/Resources/allthis.json b/RSParser/Tests/RSParserTests/Resources/allthis.json deleted file mode 100644 index ee6f0fcc6..000000000 --- a/RSParser/Tests/RSParserTests/Resources/allthis.json +++ /dev/null @@ -1 +0,0 @@ -{"description": "I just said what I said and it was wrong. Or was taken wrong.", "feed_url": "http://leancrew.com/all-this/feed.json", "title": "And now it’s all this", "items": [{"title": "Last thoughts on modifier keys", "url": "http://leancrew.com/all-this/2017/11/last-thoughts-on-modifier-keys/", "author": {"name": "Dr. Drang"}, "summary": "The first shall be last.", "date_published": "2017-11-23T21:08:29+00:00", "id": "http://leancrew.com/all-this/2017/11/last-thoughts-on-modifier-keys/", "content_html": "

When I wrote the post about ordering Mac modifier keys a few days ago, I was thinking primarily about the proper order of the symbols when writing about a keyboard shortcut, like ⌃⌥⌘P.1. I mentioned parenthetically that this order isn’t always observed when people speak about keyboard shortcuts or when they write the names of the keys out fully, as in “Command-Shift-3 takes a screenshot.”

\n

Jason Snell, in both a post at Six Colors and in conversation with John Siracusa on the lastest episode of Upgrade, took a stand against Apple’s ordering:2

\n
\n

Command is the commander! Command is the monarch of all keys! Command always comes first, in my book.

\n
\n

Siracusa agreed, and so do I. The ⌘ key is, and has always been, the key that signals a keyboard shortcut. While other modifier keys are sometimes used without ⌘—in cursor control and text selection, for example—I can’t think of any Apple applications that don’t use ⌘ to signal a keyboard shortcut for a menu item. And that primacy in shortcuts to menu items is, I think, why Apple puts it last rather than first.

\n

Keyboard shortcuts are always presented right-justified along the right edge of the menu. The most common shortcuts are just ⌘ and a letter, like ⌘N to start a new document, for example. It’s typically the variations on the basic command that get additional modifier keys, like ⌥⌘N to start a new project. If that were presented in a menu as ⌘⌥N, the menu would look wrong because the ⌘ symbols wouldn’t line up.

\n

Here’s the File menu in Safari:

\n

\"Safari

\n

There are two different New commands and three different Close commands. This, in Apple’s opinion (and mine), wouldn’t be right:

\n

\"Altered

\n

It’s not just having the ⌘ symbols aligned. The additional modifier symbols go in front because ⌘ is king and must sit next to the N or the W. The importance of the modifier decreases as you move away from the letter.

\n

It should go without saying—but I’ll say it anyway—that the letter (or number or whatever) key is the most important because nothing happens until it’s pressed.

\n

Having said all this, and despite agreeing with Apple’s symbol ordering, my ear for shortcut ordering works just like Jason’s and John’s. The main reason I use keyboard shortcut symbols in my posts instead of words is that I can read ⌥⇧⌘W and not be bothered because I don’t “hear” it as I read the symbols. “Option-Shift-Command-W,” on the other hand, gets sounded out in my head, and it sounds wrong.

\n

I suspect that’s why Apple’s own documentation sometimes gets the order wrong when the modifiers get written out as words. In speaking out the keys, “Command” is natural to put first because it announces that what’s coming is a keyboard shortcut.

\n
\n
\n
    \n
  1. \n

    Which happens to be the shortcut I use for previewing a blog post locally before publishing it. ↩︎

    \n
  2. \n
  3. \n

    In the original post, I said I didn’t know where the order was documented. A few people pointed me to both the Human Interface Guidelines and the Style Guide, where Apple gives the proper order explicitly. ↩︎

    \n
  4. \n
\n

\n

[If the formatting looks odd in your feed reader, visit the original article]

"}, {"title": "My next Mac", "url": "http://leancrew.com/all-this/2017/11/my-next-mac/", "author": {"name": "Dr. Drang"}, "summary": "Apple isn't making it easy to choose a Mac and hasn't for a few years.", "date_published": "2017-11-22T22:04:57+00:00", "id": "http://leancrew.com/all-this/2017/11/my-next-mac/", "content_html": "

Will probably be an iMac. I guess that spoils the suspense, doesn’t it?

\n

My iMac at work is the 27″ Late 2012 model, the one that came out one step before Retina came to the iMac. I don’t regret buying it, as my previous iMac (a 2006 model, I think) was absolutely on its last legs—constantly swapping to hard disk and running hot. I hadn’t meant to wait so long to replace it, but there was a long delay before that 2012 model came out, and I didn’t want to buy something that would be last year’s model almost as soon as I set it up.

\n

My home Mac is the venerable 2010 13″ MacBook Air, the first good Air. In the normal course of things, this would be the Mac I replace next, and I’ve been expecting to do so for a few years now. but…

\n

But Apple never came out with a Retina MacBook Air, choosing instead to go with the MacBook, which I find a little too far on the portable side of the portability/power spectrum. A couple of years ago, I had a crisis when my Air crapped out on me. It seemed wrong to put money into a five-year-old machine, but I wasn’t enthused about any of the MacBooks in the lineup at the time. I didn’t know the just-released 2015 MacBook Pro would turn out to be the best laptop ever made, I didn’t want to spend MacBook Pro money on my home/travel machine.

\n

The $280 logic board upgrade turned out to be a good investment, as I’m now 2½ years into my rejuvenated Air. Yes, it takes a while to wake up when I open the lid. Yes, its 128 GB SSD is tiny. No, it can’t take advantage of many of the iOS integration features that newer Macs can. And no, I don’t think it’s a good idea to install High Sierra on it. But it’s given me 30 months of faithful use, much more than I expected at the time.

\n

The announcement of the Touch Bar last year made me certain I’d be getting a MacBook Pro with it. A software-configurable set of controls seemed perfect for someone who’s always ginning up little scripts. But no one seems to like it, possibly because its configurability isn’t especially open to users. Bummer.

\n

I’ve delayed the decision on my home Mac for such a long time that now my office Mac is long in the tooth, too. Still working fine for most tasks, but just a Core 2 Duo machine that often makes me wait to scroll through long PDFs of scanned engineering and architectural drawings, something I need to do at work quite often. And no Retina.

\n

So it looks like my best bet is to buy a new iMac for work and bring my current office iMac home. This will put the power where I need it the most and will give me extra ooomph here at home. Especially with disk space (3 TB vs. 128 GB) and RAM (24GB vs. 4GB).

\n

It will be weird, though, as I haven’t had a desktop computer here at home in a dozen years. Will I enjoy being tethered to one spot in the house? And what about a travel computer?

\n

Both of these questions are made less pressing by the device I’m typing this on: a 9.7″ iPad Pro. While I agree with Gabe that it is by no means a Mac substitute, it can handle a lot of what I do at home and virtually everything I need to do on the road.1

\n

As for which iMac, I think I’ll settle on a middle-of-the-road 27″ configuration with a 3TB Fusion drive. Sort of the 2017 of what I bought in 2012.

\n
\n
\n
    \n
  1. \n

    I bought the iPad Pro last year as a sort of experiment to find out how comfortable I’d be working on it. I intend to write a full post about the results of that experiment soon, but in the meantime, you really should read Gabe’s post over at Macdrifter. I’ll probably use his post as a jumping-off point. And there may be a quiz. ↩︎

    \n
  2. \n
\n

\n

[If the formatting looks odd in your feed reader, visit the original article]

"}, {"title": "Modifier key order", "url": "http://leancrew.com/all-this/2017/11/modifier-key-order/", "author": {"name": "Dr. Drang"}, "summary": "Writing about Mac keyboard shortcuts? Make sure you put them in canonical order.", "date_published": "2017-11-20T02:22:59+00:00", "id": "http://leancrew.com/all-this/2017/11/modifier-key-order/", "content_html": "

If you write about Mac keyboard shortcuts, as I did yesterday, you should know how to do it right. Just as there’s a proper order for adjectives in English, there’s a proper order for listing the modifier keys in a shortcut.

\n

I haven’t found any documentation for this, but Apple’s preferred order is clear in how they show the modifiers in menus and how they’re displayed in the Keyboard Shortcuts Setting.

\n

\"Canonical

\n

The order is similar to how you see them down at the bottom left of your keyboard.

\n

\"Modifier

\n

Control (⌃), Option (⌥), and Command (⌘) always go in that order. The oddball is the Shift(⇧) key, which sneaks in just in front of Command.

\n

Keyboard Maestro recognizes this standard order and presents its “hot key” shortcut the same way.

\n

\"Keyboard

\n

(When people speak about keyboard shortcuts, it’s not uncommon to put Command first, e.g., “Command-Shift-3 takes a screenshot.” I’ve seen it written out that way, too. Apple is usually pretty careful to use the same order when using words as when using symbols. This page, for example, uses “Shift-Command-3,” to match the ⇧⌘3 you’d see in the Keyboard Shortcut Settings. But even Apple slips up. On the grand Mac keyboard shortcut page, there are a few instances of “Command-Shift” instead of “Shift-Command.”)

\n

The last bit of standard syntax is that the letter key in the shortcut (if there is a letter) is always presented as a capital, even when the Shift key isn’t used. I suspect this was a serious topic of discussion at Bandley 3 back in the early 80s as the Mac was being developed. They got it right. When entering a keyboard shortcut, you’re not typing a letter, you’re pressing a set of physical keys on the keyboard in front of you. The symbols on the letter keys are capitals, so that’s the appropriate way to identify those keys.


\n

[If the formatting looks odd in your feed reader, visit the original article]

"}, {"title": "Command-E", "url": "http://leancrew.com/all-this/2017/11/command-e/", "author": {"name": "Dr. Drang"}, "summary": "One of my favorite and little-known (even by Apple) Mac system level keyboard shortcuts.", "date_published": "2017-11-19T02:02:50+00:00", "id": "http://leancrew.com/all-this/2017/11/command-e/", "content_html": "

Earlier this evening, Merlin tweeted out some advice we should all heed:

\n

Per today's @thetalkshow, here's an Apple Support doc that can change your life. I dare you not to find something new here.https://t.co/37FuPPUbYK

— Merlin Mann (@hotdogsladies) November 18, 2017
\n\n

Even though I’ve been using a Mac for 32 years (albeit with an 8-year Linux hiatus) there were a few keyboard shortcuts in there I didn’t know about. And, just as important, several that I once knew but had fallen out of the habit of using.

\n

But one of my favorites—and a big convenience when writing code or prose—isn’t on the Mac keyboard shortcut page. I wonder if the folks at Apple have forgotten it.

\n

It has to do with finding text in a document. You know about ⌘F to specify a Find. That usually brings up a window or other control into which you can type (or paste) the text you’re going to be searching for. Often, there are options for controlling how the search will be performed and for replacement text. Here’s the Find window from BBEdit,

\n

\"BBEdit

\n

here’s the Find window from Pages,

\n

\"Pages

\n

and here’s TextEdit, which pops up a little Find section in the main window below the ruler,

\n

\"TextEdit

\n

You probably also know about ⌘G, which is usually given the menu name Find Next. It repeats the last Find command, allowing you to step through all the occurrences of that text in the document. There are usually other ways to cycle through all the found instances—Pages and TextEdit have arrow buttons you can click on—but ⌘G is the traditional way.1

\n

A common situation, especially when looking through long documents or source code files, is to see a particular string of text and want to find other instances of it. You may, for example, see the definition of a function and want to search out where that function gets called later in the code. One way to do that would be to select the function name, copy it, ⌘F to bring up the text entry field, paste, and then ⌘G your way through the rest of the document.2 But the copy/⌘F/paste dance is a little clumsy.

\n

Which is where ⌘E, which is typically given a menu name like Use Selection for Find, comes in.

\n

\"BBEdit

\n

Select the text you want to search for and hit ⌘E. That turns the selection into the search text, and you can go straight to ⌘G to walk through all the other occurrences.

\n

I use ⌘E all the time when searching through code for function and variable names, as in the example above. It also works in Safari, Chrome, Preview, and PDF Expert, so I do similar quick searching on words I come across as I read news articles, blogs, reports, papers, etc.

\n

⌘E works in other well-behaved applications, too, so you should give it a try. Note that “well-behaved applications” excludes MS Word. In Word, ⌘E centers the paragraph of text containing the selection. Of course.

\n
\n

Update Nov 19, 2017 8:40 AM
\nA couple of things I forgot to include:

\n

First, ⌘E in the Finder has, somewhat ironically, no relationship to finding. It ejects or unmounts the selected disk, assuming there is a selected disk. This shortcut is listed on Apple’s Mac shortcut page (which is why I was going to mention it), and a couple of people reminded me of it after the post was published. I guess I forgot about it because it’s a command I never use; I eject by right-clicking and selecting Eject from the menu that pops up.

\n

Second, editors and word processors on iOS have not inherited this useful shortcut from the Mac. There is, of course, no ⌘ key on the software keyboard, but many of the traditional Mac shortcuts that use ⌘, ⌥, and ⇧ for moving the cursor and selecting text do work on iOS when you use an external keyboard. ⌘F and ⌘G (and its backward cousin, ⇧⌘G) often work in iOS, too, but ⌘E didn’t make the cut. Too bad.

\n

\"Keyboard

\n
\n
\n
\n
    \n
  1. \n

    I believe ⌘G was originally used as the shortcut for Go To Page… in MacWrite, but it’s been Find Next for ages. 

    \n
  2. \n
  3. \n

    Yes, source code editors in general, and BBEdit in particular, have other ways to find all the instances of a string of text, and those ways may be more appropriate in some situations. But stepping your way through each instance in turn is often the best way to see the string in full context. 

    \n
  4. \n
\n

\n

[If the formatting looks odd in your feed reader, visit the original article]

"}, {"title": "Converting fractions to decimal values", "url": "http://leancrew.com/all-this/2017/11/converting-fractions-to-decimal-values/", "author": {"name": "Dr. Drang"}, "summary": "A Keyboard Maestro macro for cleaning up numerical values.", "date_published": "2017-11-13T04:35:39+00:00", "id": "http://leancrew.com/all-this/2017/11/converting-fractions-to-decimal-values/", "content_html": "

Recently, I’ve been getting Excel spreadsheets from clients that contain measurements in inches and/or fractions of an inch. In some cases, the cells with these measurements actually contain numbers but are displayed as fractions using one of the many formatting options Excel has.

\n

\"Excel

\n

In these cases, it’s easy for me to change the formatting to a standard decimal representation before exporting the sheet as a CSV file.

\n
\n

Aside
\nThe only reason I own Excel is to be able to open spreadsheets that other people send me and export them into some other usable format. This wasn’t always the case. I used the hell out of Excel in the late 80s and early 90s, when it was still a proper Mac app. But somewhere there in the early to mid 90s, Microsoft turned Excel into a Windows app that ran on the Mac and ruined its user interface. I stopped using it shortly thereafter. In the quarter-century since, it’s only gotten worse. Note, for example, the absurd layout of the dialog box above.

\n

Small Excel spreadsheets usually get moved over to Numbers, a considerably less powerful spreadsheet, but one that works the way a normal Mac app works. Large Excel spreadsheets get converted to CSV (often after a short visit to Numbers, as I’ve had trouble with Excel’s CSV conversion) so I can query and manipulate the data with Pandas. Why don’t I just open the Excel file in Pandas directly? Because the spreadsheets I get are so filled with cruft, read_excel can’t be trusted.

\n
\n

Often, though, the measurements are entered as text. This is done by prefixing the data with a single quote mark, e.g.,

\n
'2 5/16\n
\n

or maybe

\n
'2-5/16\n
\n

These need to be converted into a decimal representation before exporting to CSV, so Pandas will recognize them as numbers rather than strings.1

\n

To handle this situation, I created a Keyboard Maestro called Floatize. To use Floatize, I

\n
    \n
  • Select the column of data I want to convert.
  • \n
  • Copy it to the clipboard.
  • \n
  • Run Floatize, which converts the clipboard from a fractional representation to decimal.
  • \n
  • Paste the result back into the column.
  • \n
\n

Here’s the macro:

\n

\"Floatize

\n

It takes the clipboard, sends it to a script as standard input, and then puts the output back into the clipboard. Here’s the Python script it runs:

\n
python:\n 1:  #!/usr/bin/env python\n 2:  \n 3:  from fractions import Fraction\n 4:  import re\n 5:  import sys\n 6:  \n 7:  def floatize(s):  \n 8:    if s in ['', 'n/a', 'none']:\n 9:      return ''\n10:    else:\n11:      try:\n12:        num = float(s)\n13:      except ValueError:\n14:        t = re.sub(r'(.*\\d) *- *(\\d.*)', r'\\1 \\2', s)\n15:        if t[0] == '-':\n16:          num = -float(sum(Fraction(x) for x in t[1:].split(None, 1)))\n17:        else:\n18:          num = float(sum(Fraction(x) for x in t.split(None, 1)))\n19:      return \"{:f}\".format(num)\n20:    \n21:  for line in sys.stdin.read().splitlines():\n22:    item = line.strip().lower()\n23:    print floatize(item)\n
\n

The bulk of the fraction parsing is done by the fractions module in either Line 16 or Line 18. The rest of the script is an attempt to manipulate the oddball types of data I get into a format that fractions understands. Here’s what it can handle:

\n
    \n
  • Blank, n/a or none entries get turned into an empty string (Lines 8–9). Note that entries are converted to lower case in Line 22, before being passed to the floatize function, so this handles N/A and None, too.
  • \n
  • Entries already in decimal format get converted to a float (Line 12) and then output in decimal format (Line 19). This is basically a pass-through.
  • \n
  • Entries with a dash between the whole and fractional parts get the dash removed (Line 14).
  • \n
  • Negative entries (Line 15) have their whole and fractional parts added together with the sign reversed (Line 16), and then output in decimal format (Line 19).
  • \n
  • Positive entries have their whole and fractional parts added together (Line 18), and then output in decimal format (Line 19).
  • \n
  • Entries that don’t look like any of the above cause an error.
  • \n
\n

The macro works perfectly in Numbers and BBEdit, but not in Excel. For some reason, nothing happens to the clipboard if I run the Floatize macro while Excel is the active application. I can, however, copy the column of fractions, activate another app, run Floatize, switch back to Excel and paste the converted clipboard. Another example of Excel not acting like a proper Mac app and another reason I move data out of Excel as quickly as I can.

\n

Despite its inability to work inside Excel, this macro has been very handy over the past couple of weeks. I’ve used it to clean up several data sets that were loaded with fractions.

\n

By the way, I will block anyone who tweets that I wouldn’t have this problem if I used the metric system.

\n
\n

Update Nov 13, 2017 10:04 PM  \nThe Twitter consensus is my problem in using this macro in Excel stems from Excel’s clipboard dumbassery. It apparently uses a private clipboard instead of the system clipboard until you bring another application to the front. Hence the macro’s inability to convert the clipboard unless I switched away from Excel.

\n

Jimmy Hartington suggested this workaround:

\n
\n
\n@drdrang Excel does something weird with the clipboard as you noticed.
Try this. Copy in Excel. Press Escape. Run the macro.
\nBy pressing Escape I think Excel stops messing with the clipboard.\n
Jimmy Hartington (@jimmyhartington) Nov 12 2017 11:06 PM
\n
\n
\n

This works perfectly and pointed me in the direction to make a better version of the macro, one that does all the copying, converting, and pasting:

\n

\"New

\n

Note the conditional action that simulates the pressing of the Escape key only if Excel is the front application. This is necessary because Escape causes Numbers to deselect the column, which screws up the paste action at the end of the macro.

\n

With this macro I can now select the column that needs converting and just press ⌃⌥⌘F to do the conversion in a single step. Thanks, Jimmy!

\n
\n
\n
\n
    \n
  1. \n

    There are probably ways to get Pandas to do the conversion as it imports the data, but I feel more comfortable—more in control—if I do it ahead of time. ↩︎

    \n
  2. \n
\n

\n

[If the formatting looks odd in your feed reader, visit the original article]

"}, {"title": "A modest proposal", "url": "http://leancrew.com/all-this/2017/11/a-modest-proposal/", "author": {"name": "Dr. Drang"}, "summary": "MacStories should think about cleaning up some of its Apple sales charts.", "date_published": "2017-11-07T03:27:11+00:00", "id": "http://leancrew.com/all-this/2017/11/a-modest-proposal/", "content_html": "

With Apple sales and graphing on my mind, I’d like to make a small complaint and suggestion to the folks at MacStories: some of your quarterly graphs could use a little scrubbing; they’d be much easier on the eyes if they were cleaned up a bit.

\n

I’m talking about the ones that look like this:

\n

\"MacStories

\n

My complaint is with the clutter along the x-axis. That uniform gray mass of text detracts from the data above it. And although putting a label at each quarter seems to provide more information to the reader, the reader actually gets less out of it because it’s too hard to read. The axis would be improved if it had just yearly Q1 labels. Maybe even one label every other year. Fewer labels would mean they could be turned back horizontal, which would be easier to read and would give more vertical space for the data.

\n

I suspect every data point is labeled because that’s the way Numbers wants to do it, and MacStories’ automated system for generating these graphs follows the Numbers defaults. But if you were making these graphs by hand, you’d never label the x-axis this way. An automated system shouldn’t, either.

\n

On the Mac, Numbers has a way to skip labels on the x-axis by selecting Custom Category Intervals from the Category Labels popup menu and then choosing how often to put the labels.

\n

\"Mac

\n

Sadly, I can’t find that option in the iOS version. Maybe it’s tucked away in a less obvious place.

\n

\"iOS

\n

Personally, I’m not a fan of either version of Numbers when it comes to making graphs because I like way more control than they give. But Numbers (like most graphing programs and libraries) can make nice graphs if you’re willing to break away from its defaults.

\n

I don’t really want to pick on MacStories. Their graphs are as good as, if not better than, what you typically find in blogs and news sites. Most graphs, unfortunately, are based on the default settings of the software that made them, and come out looking a little clumsy.

\n

I read MacStories because it has stylish writing. I’d like its graphs to be just as stylish.

\n
\n

Update Nov 7, 2017 8:55 PM
\nI was informed on Twitter by two authoritative sources (this one and this one) that the MacStories graphs are made with Excel, not Numbers. I haven’t used Excel for graphing since the mid-90s, but even then it had more customization options than Numbers has now. So I’m crossing my fingers for cleaner x-axes in January’s graphs.

\n

\n

[If the formatting looks odd in your feed reader, visit the original article]

"}, {"title": "Apple sales graphs and the iPhone 7", "url": "http://leancrew.com/all-this/2017/11/apple-sales-graphs-and-the-iphone-7/", "author": {"name": "Dr. Drang"}, "summary": "Obligatory quarterly post.", "date_published": "2017-11-06T02:53:19+00:00", "id": "http://leancrew.com/all-this/2017/11/apple-sales-graphs-and-the-iphone-7/", "content_html": "

Last week Apple released its 2017 Q4 (which everyone else’s calendar says is 2017 Q3) sales and revenue figures, which means a boatload of graphs from all your fave rave Apple-oriented bloggers. I’m a few days late, as usual, despite having long ago written scripts to generate these graphs.

\n

I don’t make as many graphs as everyone else, partly because I’m lazy, but mainly because don’t care about revenue. My concern is the popularity of the devices I use, not how expensive they are. I want third-party developers to keep writing apps for them, and that means there has to be a market for them out there.

\n

Here’s the summary graph, with the sales figures for the three main Apple products. The dots are the quarterly sales and the lines are the four-quarter moving averages.

\n

\"Apple

\n

The iPhone dominates the scale of this graphs, so it’s helpful to also see the iPad and Mac on their own graphs.

\n

\"iPad

\n

\"Mac

\n

The good news is the second straight quarter of year-over-year increase in iPad sales after its long, well-documented slide since 2013. Additional good news is the Mac’s slow but consistent rise over the past year—four straight quarters of year-over-year sales increases. Imagine how much better the figures would be if people really liked the Touch Bar.

\n

I think its fair to say, though, that the iPhone 7 was something of a dud. It had a good start, but its non-intro quarters had almost exactly the same sales as the 6S had. Look at the final three quarters of each.

\n

\"Apple

\n

As I’ve argued before, the iPhone 6S sales looked lackluster only in comparison to the gangbusters popularity of the iPhone 6. If one ignored the year of the 6, the 6S’s sales were more or less on the same upward trend as the previous three editions. The iPhone 7 was on that same trend for its first quarter but went dead flat after that.

\n

Apple’s report in January will be very interesting.


\n

[If the formatting looks odd in your feed reader, visit the original article]

"}, {"title": "Another one-off Keyboard Maestro macro", "url": "http://leancrew.com/all-this/2017/11/another-one-off-keyboard-maestro-macro/", "author": {"name": "Dr. Drang"}, "summary": "I should do more of these.", "date_published": "2017-11-03T02:02:41+00:00", "id": "http://leancrew.com/all-this/2017/11/another-one-off-keyboard-maestro-macro/", "content_html": "

Do you use Keyboard Maestro (or AppleScript or whatever) for one-time, throwaway macros as often as you should? I know I don’t, but I did put one together a couple of days ago and used a feature I’d never tried before.

\n

I had one of those recalcitrant PDFs that I often get from clients. This one was 25–30 pages long, each an E-sized floor plan drawing for a building. The drawings were all black-and-white, but the PDF had color annotations added. I needed to add my own annotations to most of the pages, but something about the format of the file made it very cumbersome to work with. I tried Preview, PDF Expert, and PDfpen Pro, and they all were glacially slow when panning, zooming, and switching pages.

\n

So I broke the file up into individual pages using PDFtk:

\n
pdftk drawings.pdf burst\n
\n

The single-page files didn’t make me wait for the spinning beach ball, so I was able to add my annotations quickly in PDF Expert. Then came an impasse.

\n

I wanted to email the drawings with my annotations back to the client and to some other parties, but they were too big to fit in an email. I could use multiple emails, but that’s a recipe for losing some of the files. I could use a Dropbox link, but I had a sense that one of the other parties wouldn’t understand how that worked. What seemed best was to convert the files to JPEGs at the lowest legible resolution, zip them together, and send the zipped file in a single email.1

\n

My normal practice would be to use sips for this, because I can issue a single command to convert any number of files. But I soon learned that sips doesn’t handle annotations properly when converting the format of a file from PDF to JPEG. In my brief testing, I found that neither my annotations or the ones that came from the client were visible in the converted JPEGs.

\n

Preview, though, can export a PDF as a JPEG with the annotations intact and visible. Which presumably means that sips and Preview are using different code bases for the conversion. Whatever.

\n

The problem with using Preview is I’d have to convert every file by hand. Not the most burdensome job I’ve ever had, but one that’s boring and susceptible to error. Enter Keyboard Maestro.

\n

Here’s the macro that exports the current file in Preview to a JPEG and closes it:

\n

\"Convert

\n

You’ll note there’s no step for setting the resolution for the exported file. That’s because once it’s set, it doesn’t change from one export to the next.

\n

I didn’t try to have the macro open each PDF in turn, because I didn’t trust myself to do that right. I just opened all of them and then ran the macro by pressing ⌃⌥⌘J repeatedly. The macro closed each file after exporting it, leaving the next window ready to be operated on.

\n

The new (for me) thing with this macro was the “and drag to” part at the bottom of the third step. That’s what moved the popup menu selection from PDF up to JPEG.

\n

\"Preview

\n

I figured out how much to drag by taking a screenshot like the one above and using a selection in Acorn to measure the vertical distance from the center of PDF to the center of JPEG. No trial and error.

\n

With 25–30 files to convert, it’s possible I did save time with this macro. But the main reason I made it was to avoid the tedium and the likelihood of error on my part. These are not independent—I’m far more prone to make errors when the task is repetitive and doesn’t maintain my attention.

\n
\n
\n
    \n
  1. \n

    No, a Dropbox link is really no more complicated than a zip file, but zip files are more familiar to more people. And although zipping JPEGs doesn’t make them smaller, it does package them up in a way that naive Windows users are usually comfortable with. It would be lovely if I always worked with people whose computer skills were trustworthy, but that’s not the world I live in. ↩︎

    \n
  2. \n
\n

\n

[If the formatting looks odd in your feed reader, visit the original article]

"}, {"title": "Binomial baseball", "url": "http://leancrew.com/all-this/2017/10/binomial-baseball/", "author": {"name": "Dr. Drang"}, "summary": "Teams with a 3–2 lead in the World Series win less often than you might think.", "date_published": "2017-10-30T18:14:47+00:00", "id": "http://leancrew.com/all-this/2017/10/binomial-baseball/", "content_html": "

While reading a recap of last night’s World Series games, I saw this statistic: of the 65 Series that have had a sixth game, the team with the 3–2 lead has won the Series 43 times. This was, I think, intended to show us that the Astros have a strong chance to beat out the Dodgers for the title. And they do. But not as strong as you might expect.

\n

If the teams were evenly balanced and each game independent of the others, we would expect the team with the 3–2 lead to win 75% of the time. 50% of the time they’d win the sixth game and the Series would be over; 25% of the time (50% of the other 50%) they’d win in the seventh game. So the leading team “should” have won the Series 48 or 49 times out of 65, not 43 times.

\n

Is this 5 or 6 game difference meaningful? For that we need to do some calculations using the binomial distribution. Python’s SciPy set of libraries has a subsection of statistical modules, including one for binomial distribution calculations. We can import it this way:

\n
python:\nfrom scipy.stats import binom\n
\n

Let’s start by figuring out the probability that the leading team would win 43 times in 65 trials. With a 75% probability of winning the Series in each trial, the probability of 43 Series wins in 65 chances is calculated through

\n
python:\nbinom.pmf(43, 65, .75)\n
\n

where the pmf function gets its name from the standard abbreviation for “probability mass function.” The answer is 0.029 or just under 3%. This makes it seem very unlikely that our assumption of 50–50 games would lead to only 43 Series wins for the leading team.

\n

But that isn’t the way these sorts of calculations are normally done. If we want to find out if a seemingly out-of-whack result is “statistically significant,” we should look at the probability of results that are at least as far away from our expectations as the actual result was. In our case, that means looking not only at the probability of 43 Series wins out of 65 chances, but also 42 wins, 41 wins, and so on. We then add up all of these “at least as weird” probabilities.

\n

The usual terminology for this sort of summation is “cumulative distribution function,” and the binom module has a function for it:

\n
python:\nbinom.cdf(43, 65, .75)\n
\n

The result is 0.0695, or about 7%. Another way of looking at this is that if our assumption of 50–50 games were correct, there’s a 93% chance that the leading team would win the Series more than 43 times in 65 chances.

\n

In hypothesis testing, the value 0.0695 is called the p-value,\nand it’s common in many fields to consider a result statistically significant if its p-value is less than 0.05. Using that criterion, we would not take the difference between our “null hypothesis” of 50–50 games and the World Series history as statistically significant.1

\n

But it’s something for Dodgers fans to cling to.

\n
\n
\n
    \n
  1. \n

    Yes, I’ve been a little breezy here with my definition of null and alternate hypotheses and one-sided vs. two-sided rejection areas, but it’s just baseball. ↩︎

    \n
  2. \n
\n

\n

[If the formatting looks odd in your feed reader, visit the original article]

"}, {"title": "Judas", "url": "http://leancrew.com/all-this/2017/10/judas/", "author": {"name": "Dr. Drang"}, "summary": "I don't believe you.", "date_published": "2017-10-28T14:33:47+00:00", "id": "http://leancrew.com/all-this/2017/10/judas/", "content_html": "

I can’t say I wasn’t warned. The concert’s promotional artwork (especially those fonts) and the previous two albums, Shadows in the Night and Fallen Angels, were strong clues that I was going to be seeing a Bob Dylan infected by the Great American Songbook.

\n

\"Dylan

\n

So yes, we got “Melancholy Mood” and “Autumn Leaves” and “The September of My Years” with Bob crooning into a big microphone and holding its stand at a jaunty angle. But that’s not what I disliked about the concert. In fact, those songs were done pretty well, and Dylan’s voice sounded better than any previous concert I’ve been to. It was what he did to his own songs that was frustrating.

\n

No one in his right mind goes to a Dylan concert expecting to hear his classics played or sung the way they were done originally. But the treatment they’re given is always interesting and usually fun. Not this time. Apart from “It Ain’t Me, Babe” and “Highway 61 Revisited,” the Dylan songs suffered from arrangements that were, I guess, meant to match the style of the non-Dylan songs. Under these conditions, you can’t expect the songs to rock, but you can expect them to swing. And the band just didn’t swing.

\n

There was a sameness to Charlie Sexton’s guitar work throughout the night. Despite changing instruments several times, his sound was constrained and repetitive. But when a band doesn’t swing, it’s mainly because of the rhythm section. These are talented musicians who can play in any style, so if they didn’t find a groove it must have been because Bob didn’t want them to.

\n

As a result, he lost the audience. Many left early and those who hung in there stayed glued to their seats until the encores. Can you imagine a Dylan audience not standing to “Tangled Up in Blue”?

\n

The optimist in me notes that Dylan never sticks with anything for long, and after two albums of standards he may be ready for the next thing. I hope he comes back to the notion that that it’s his songs, and the blues and country/folk they came out of, that are truly the Great American Songbook and gives them the treatment they deserve.


\n

[If the formatting looks odd in your feed reader, visit the original article]

"}, {"title": "Icons", "url": "http://leancrew.com/all-this/2017/10/icons/", "author": {"name": "Dr. Drang"}, "summary": "Brett Terpstra's little app has a strangely familiar icon.", "date_published": "2017-10-27T01:39:06+00:00", "id": "http://leancrew.com/all-this/2017/10/icons/", "content_html": "

I have some code sitting around here somewhere for extracting the images (app icons, book covers, album art, etc.) from the various iTunes and App Stores. But I think it’ll be easier to use Brett Terpstra’s iTunesIcon Automator app, which just got an update.

\n

Apart from its appeal to my laziness, Brett’s little app has a cool icon that I took an instant liking to.

\n

\"iTunesIcon

\n

If you work around industrial and construction equipment, Brett’s icon should bring a smile of familiarity. It’s highly reminiscent of the hands in safety signs and decals that are in dire straits.

\n

\"Safety

\n

Images from SafetySign.com

\n

I’m sure your fingers are safe when using iTunesIcon.


\n

[If the formatting looks odd in your feed reader, visit the original article]

"}, {"title": "Feed reading", "url": "http://leancrew.com/all-this/2017/10/feed-reading/", "author": {"name": "Dr. Drang"}, "summary": "My feed reading setup is basically complete", "date_published": "2017-10-23T00:10:42+00:00", "id": "http://leancrew.com/all-this/2017/10/feed-reading/", "content_html": "

Gabe Weatherhead has a nice article today on how he uses Feedbin to handle his reading on the web. If you follow Gabe—and if not, why don’t you?—it will not surprise you to learn that he’s formidably organized. I don’t see myself following in his footsteps, but it’s always useful to learn how smart people do things. His article also reminded me that I’ve been meaning to write about my feed reading setup.

\n

A couple of years ago, I let my subscription to Feed Wrangler lapse and started using a homemade, web-based RSS reading system. The heart of the system is still the script described in this post, but with a some changes as I thought of better ways of doing things.

\n

The biggest change came in the past few months. Initially, I created a single static web page with all of today’s articles from the feeds I subscribe to. A cron task updated the page a few times an hour throughout the day. In this system, “today” was defined as “from 10:00 pm last night until now,” and the page would grow in size from morning to night.

\n

The advantage of this temporal arrangement from a programming point of view was that I didn’t have to write any code to keep track of whether I’d already read an article or not, and there were no external dependencies. If it was published “today,” it got on the page.

\n

The disadvantage was from the reading point of view. As I visited the page throughout the day, it became more and more filled with article I’d already read. This wasn’t as terrible as you might think. The articles were arranged in reverse chronological order by publication time, so the ones I’d read were typically at the bottom of the page. I say “typically” because some feeds—XKCD comes to mind—are very bad at providing accurate publication times and their articles would sometimes end up at the bottom despite being recently published.

\n

Eliminating the reading disadvantage meant keeping track of what I’d read and showing only what I hadn’t—eliminating the programming advantage. I decided to keep track of read articles in an SQLite database and to add items to that database through a button placed at the bottom of each article on my RSS page.

\n

\"RSS

\n

This meant

\n
    \n
  1. Building a database that would uniquely identify every article. This was pretty simple. Each record has just two fields: the name of the website and the unique article ID (which is often just the article’s URL but is sometimes a long alphanumeric string generated by the site’s blogging software).
  2. \n
  3. Altering the existing script that builds the RSS page to filter out feed items that are in the database. Because Python has an SQLite module as part of its standard library and the syntax of SQL commands is straightforward, this wasn’t as tricky as I thought it would be. In fact, the new code is easier to read than the time-based filtering code I removed.
  4. \n
  5. Writing a server script (basically just a CGI script) to add an article to the database when given the blog name and article ID via the POST method. It’s been a while since I last wrote a CGI script, but it was like riding a bicycle.
  6. \n
  7. Adding some JavaScript with XMLHttpRequest to the RSS page to call the server script when a button is pressed. This took the most time, mainly because everyone in the world (except me) knows how to do AJAX now, and finding references written at an appropriately low level was harder than I expected. I found this Stack Overflow discussion helpful.
  8. \n
\n

So now I usually tap the Mark as read button when I get to the end of an article. If it’s a long article that I want to read later, I don’t mark it as read, and it’ll be there the next time I bring up the RSS page.

\n

Fearing I’d forget how to use XMLHttpRequest, I quickly included another form at the end of each article for adding that article to my Pinboard account. I didn’t bother adding labels to the text field, because I’m the only one who uses this and I know the field is for tags. I did, however, include some DOM stuff to make it obvious when I’d marked an article as read or added it to Pinboard.

\n

\"RSS

\n

What I like about this system is how portable and (I hope) future-proof it is. I’ve been reluctant to sign on with Feedbin or Feedly or BazQux or any of the other Google Reader replacements because I worry they’ll write a Medium post and disappear with my subscription list and whatever organization scheme I’ve created. My system can run on any web server with Python, SQLite, and a cgi-bin directory. I think that’ll mean “any server, anywhere” for a very long time.


\n

[If the formatting looks odd in your feed reader, visit the original article]

"}], "home_page_url": "http://leancrew.com/all-this/", "version": "https://jsonfeed.org/version/1", "icon": "http://leancrew.com/all-this/resources/snowman-200.png"} diff --git a/RSParser/Tests/RSParserTests/Resources/atp.rss b/RSParser/Tests/RSParserTests/Resources/atp.rss deleted file mode 100644 index 568e81e00..000000000 --- a/RSParser/Tests/RSParserTests/Resources/atp.rss +++ /dev/null @@ -1,4635 +0,0 @@ - -Accidental Tech Podcasthttp://atp.fm/Thu, 31 Jan 2019 04:04:10 +0000en-USSite-Server v6.0.0-16818-16818 (http://www.squarespace.com)Marco Arment, Casey Liss, John SiracusaThree nerds discussing tech, Apple, programming, and loosely related matters.Three nerds discussing tech, Apple, programming, and loosely related matters.Three nerds discussing tech, Apple, programming, and loosely related matters.noatp@marco.orgatp@marco.org311: Mutually Assured DestructionMarco ArmentThu, 31 Jan 2019 16:58:12 +0000http://atp.fm/episodes/311513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5c5273baf950b7d24bfdcb28 -
  • Follow-up:
      -
    • 👴🏻👴🏻👴🏻 screwed up "finsta"
    • -
    • Debate over the prevalence of the stage lighting problem
    • -
    • Silence of Siri and other IoT info
    • -
    • Wireless Accessory Config
    • -
    -
  • -
  • Does anyone doubt that Facebook is straight garbage? -
  • -
  • Apple's FaceTime bug -
  • -
  • Bloomberg/Gurman iPhone Scoop
  • -
  • #askatp
      -
    • Is it worth cleaning dust out of MacBooks? (via Oliver Dejon)
    • -
    • Is it worth upgrading the CPU on a family iMac? (via Jeremy Kenison) -
    • -
    • Is tweeting a good idea? (via Andrew)
    • -
    -
  • -
  • Post-show: -
  • - -

    Sponsored by:

    -
      -
    • AfterShokz: Headphones powered by bone-conduction technology. Get $50 off a Trekz Air bundle with code atpbundle.
    • -
    • Marine Layer: Clothes that make it easy to get dressed in the morning. Get 15% off your first order with code atp15.
    • -
    • Casper: Get $50 off select mattresses with code atp. Terms and conditions apply.
    • -
    ]]>
    ATPFacebook's Onavo scam, Apple's FaceTime bug, early iPhone rumors, and Overcast's Instant Search.no01:54:09
    310: Apple PlaystoreMarco ArmentThu, 24 Jan 2019 19:03:58 +0000http://atp.fm/episodes/310513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5c493ce94fa51a8e6ec4de77 -
  • SLACCIDENTAL TECH PODCAST SHIRTS!!
    Order right now or you'll miss it! Orders stop 8 PM Monday evening
  • -
  • Follow-up:
      -
    • The Terminator saga continues
    • -
    • "Apple One" feedback
    • -
    -
  • -
  • Instagram, friendstagrams, and spam accounts -
  • -
  • John's Internet of Things -
  • -
  • Casey's [Broader] Internet of Things -
  • -
  • MacBook Pro "stage light" fault and iterative development -
  • -
  • #askatp
      -
    • What happens to projects like homebrew if the ARM transition happens? (via Andrew Jaffe)
    • -
    • How do we avoid crapping on future tech that we don't inherently understand? (via John Wilander)
    • -
    • How do we handle snark? Why is it so prevalent in tech? (via Mike McPhaden) -
    • -
    -
  • -
  • Post-show: What would you say it is you do here, Casey?
  • - -

    Sponsored by:

    -
      -
    • Handy: The easy and convenient way to book home cleanings on a schedule that works for you. Get your first 3-hour cleaning for $39 when you sign up for a plan with code ATP.
    • -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    • Arq Backup: Back up all of your computers securely to your cloud account or NAS. Free 30-day trial.
    • -
    ]]>
    ATPOur internets of things, our YouTube careers, and our theories on teenagers.no02:02:39
    309: Do Not Expect Five Nines From My PlexMarco ArmentFri, 18 Jan 2019 15:47:16 +0000http://atp.fm/episodes/309513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5c41524d562fa7e089140838 -
  • Pre-show: Affluent bezelles (Overcast: bezelle, correction)
  • -
  • Follow-up: Casey's WAF-F20 waffle maker 🤤
  • -
  • John's Terminator woes -
  • -
  • iPhone Smart Battery Cases -
  • -
  • Apple's 2019: what do we want, and what will affect Apple the most? -
  • -
  • #askatp -
  • -
  • Post-show: Three middle-aged men explain Instagram to each other -
  • - -

    Sponsored by:

    -
      -
    • AfterShokz: Headphones powered by bone-conduction technology. Use code atpbundle for $50 off the Trekz Air bundle.
    • -
    • Care/of: Completely personalized vitamins and supplements delivered right to your door. Use code ATP50 for 50% off your first month.
    • -
    • Banktivity: An easy-to-use, full-featured personal finance management app exclusively for macOS and iOS. Use code ATP10 for 10% off.
    • -
    ]]>
    ATPTerminator woes, Smart Battery Cases, and the most impactful things we hope to see from Apple in 2019.no01:44:30
    308: Left-Handed Streaming ServiceMarco ArmentThu, 10 Jan 2019 19:39:30 +0000http://atp.fm/episodes/308513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5c36c483575d1f35e821f4fd -
  • Pre-show: What is a jam band, mannnnn?
  • -
  • Follow-up: -
  • -
  • Firefox is still a thing
  • -
  • Apple is putting iTunes and/or AirPlay 2 in various TVs -
  • -
  • MicroLED -
  • -
  • #askatp -
  • -
  • Post-show: Is today Mac Pro day?
  • - -

    Sponsored by:

    -
      -
    • Audible: There’s never been a better time to start listening. Get started with a 30-day trial.
    • -
    • Away: Use code ATP for $20 off a suitcase. Because this season, everyone wants to get Away.
    • -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    ]]>
    ATPApple, TVs, and Apple and TVs. But not the Apple TV.no01:44:21
    307: Casey Apple PencilMarco ArmentFri, 04 Jan 2019 13:56:39 +0000http://atp.fm/episodes/307513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5c2d887f40ec9a33ed708799 -
  • Happy new year!
  • -
  • John reviews the Tesla Model 3
  • -
  • John reviews Casey's Volvo XC90
  • -
  • John reviews Casey's iPad Pro -
  • -
  • Casey reviews his Apple Pencil -
  • -
  • Marco reviews Linea -
  • -
  • Screen protector update
  • -
  • Apple's financial news -
  • -
  • Is falling stock price going to cause a retention problem? -
  • -
  • #askatp -
  • -
  • Post show -
  • - -

    Sponsored by

    -
      -
    • Hover: Hover is turning 10 this month! Celebrate with incredible deals on domains and more.
    • -
    • Eero: Finally, Wi-Fi that works. Get $100 off a Wi-Fi system and a year of eero Plus with code ATP.
    • -
    • Linode: Instantly deploy and manage an SSD server in the Linode Cloud. Get a $20 credit with code atp2019.
    • -
    ]]>
    ATPJohn reviews Casey's stuff, Marco draws on his iPad, and Tim writes a sad letter.no01:53:21
    306: My Watch Has EndedMarco ArmentWed, 26 Dec 2018 14:17:53 +0000http://atp.fm/episodes/306513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5c1db6004d7a9c2baf304c2e -
  • Follow-up: -
  • -
  • John's 2018 Butterfly Keyboard Watch -
  • -
  • Marco's fingerprinty iPad solution -
  • -
  • John Giannandrea appointed to Apple executive team -
  • -
  • iPad Pros can ship bent... 🤷🏻‍♂️ -
  • -
  • #askatp
      -
    • Does Overcast do anything special to inhibit cookies? (via Mitch Cohen)
        -
      • Could Overcast get blocked by publishers for not implementing RAD?
      • -
      -
    • -
    • If Intel got its act together, would Apple cancel any ARM Mac plans and stick with Intel? (via Gabriel Salkin) -
    • -
    • What's the ATP editing process? (via Markus N)
    • -
    • Do Marco and Casey use Xcode in full-screen mode? (via Erik)
    • -
    -
  • - -

    Sponsored by:

    -
      -
    • Away: Because this season, everyone wants to get Away. Get $20 off a suitcase with code ATP.
    • -
    • Backblaze: Unlimited cloud backup for Macs and PCs for just $5/month. Start your 15-day free trial today.
    • -
    • Mack Weldon: Better than whatever you're wearing right now. Get 20% off your first order with code ATP.
    • -
    ]]>
    ATPBent iPads, screen protectors, a new Apple SVP, and our 2018 in review.no01:53:07
    305: An Uneasy PeaceMarco ArmentTue, 18 Dec 2018 16:26:48 +0000http://atp.fm/episodes/305513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5c186d9370a6adf7716f7419 -
  • Follow-up: -
  • -
  • Apple Music sunsets Connect
  • -
  • Microsoft Edge is coming to macOS -
  • -
  • NPR's Remote Audio Data (RAD) spec -
  • -
  • #askatp -
  • - -

    Sponsored by:

    -
      -
    • Linode: Instantly deploy and manage an SSD server in the Linode Cloud. Use code atp2018 for a $20 credit.
    • -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    • Eero: Finally, Wi-Fi that works. Get $100 off a Wi-Fi system and a year of eero Plus with code ATP.
    • -
    ]]>
    ATPNPR's RAD proposal, Microsoft Edge, some very fun domains, and a fond farewell to Apple Music Connect.no01:41:47
    304: Island of ShortcutsMarco ArmentThu, 13 Dec 2018 17:43:22 +0000http://atp.fm/episodes/304513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5c11db840e2e72a4a1d2eea6 -
  • Marco's lost video
  • -
  • Casey reviews a product that Marco loves -
  • -
  • Accidental Trunk Podcast -
  • -
  • The power of macOS -
  • -
  • Fixing a tricky Overcast crashing bug -
  • -
  • Shortcuts vs. the command line -
  • -
  • #askatp -
  • -
  • Post show Neutral: Electric cars -
  • - -

    Sponsored by:

    -
      -
    • Betterment: Outsmart average. Get up to 1 year managed free.
    • -
    • Linode: Instantly deploy and manage an SSD server in seconds. Use code atp2018 for a $20 credit.
    • -
    • Casper: Get $50 off select mattresses with code ATP. Terms and conditions apply.
    • -
    ]]>
    ATPA lost video, Shortcuts vs. the command line, fixing a tricky bug, and the debut of the Accidental Trunk Podcast.no01:57:31
    303: Your Mahogany WorldMarco ArmentThu, 06 Dec 2018 22:22:21 +0000http://atp.fm/episodes/303513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5c08a4ba03ce6456d0c74b4e -
  • Follow-up: -
  • -
  • Maybe 8K is all about @3x? -
  • -
  • More theories on Apple price increases -
  • -
  • Is Apple's prioritization of profit over marketshare the right approach? -
  • -
  • Marco flies next to a Microsoft commercial -
  • -
  • #askatp -
  • -
  • Post show: Marco's Tesla robbery -
  • - -

    Sponsored by:

    -
      -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    • Molekule: The only air purifier that actually destroys pollutants. Use code ATP for $75 off your first order.
    • -
    • Fracture: Photos printed in vivid color directly on glass. Get a special discount off your first order.
    • -
    ]]>
    ATPCasey runs ffmpeg on his iPad, John defends desserts, and Marco finds himself in a Microsoft commercial.no02:12:44
    302: Paint This SquareMarco ArmentWed, 28 Nov 2018 16:42:08 +0000http://atp.fm/episodes/302513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5bfe1892c2241b299ccfd40c -
  • Follow-up: -
  • -
  • What happened to 5K displays? -
  • -
  • Apple Entrepreneur Camp -
  • -
  • How much does it cost to get a Mac that performs as well as an iPad Pro? -
  • -
  • Apple's ever-increasing prices -
  • -
  • #askatp -
  • -
  • Post show: Thanksgiving Desserts of Siracusa County -
  • - -

    Sponsored by:

    -
      -
    • Linode: Instantly deploy and manage an SSD server in the Linode Cloud. Get a server running in seconds with your choice of Linux distro, resources, and node location.
    • -
    • Jamf Now: Set up and protect your Apple devices at work, from anywhere. First 3 devices are free.
    • -
    ]]>
    ATPApple Entrepreneur Camp, 5K vs. 8K displays, iPad vs. Mac performance, why Apple's prices are increasing, and Italian-American desserts.no02:08:55
    301: I Cut Them Up in the AirMarco ArmentTue, 20 Nov 2018 16:50:02 +0000http://atp.fm/episodes/301513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5bf3868d2b6a285fc81d013d -
  • Essential's "audiophile" headphone-bump dongle
  • -
  • Follow-up: Total ATP length so far
  • -
  • The Surface Go is more interesting than we thought
  • -
  • An update on youtube-dl and iSH
  • -
  • Google Photos has let Casey down
  • -
  • #askatp -
  • -
  • Post show: ATP Thanksgiving -
  • - -

    Sponsored by:

    -
      -
    • RXBAR: Whole food protein bars made with 100% real ingredients. For a limited time, US customers can get 25% off your first order of the best-seller variety pack with code ATP.
    • -
    • Betterment: Outsmart average. Get up to 1 year managed free.
    • -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    ]]>
    ATPAudiophile headphone bumps, the Surface Go, adventures with Google Photos, and an ATP Thanksgiving.no01:48:37
    300: First of All, It's EasyMarco ArmentThu, 15 Nov 2018 14:45:19 +0000http://atp.fm/episodes/300513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5beba5788985838b6f5af3b9 -
  • 🎉 300th Episode Spectacular! 🎉 -
  • -
  • Follow-up: -
  • -
  • iPad Pro impressions -
  • -
  • MacBook Air impressions
  • -
  • #askatp
  • -
  • Post-show: Marco's adventures on YouTube -
  • - -

    Sponsored by:

    -
      -
    • Linode: Instantly deploy and manage an SSD server in the Linode Cloud. Get a $20 credit with code atp2018.
    • -
    • Fracture: Photos printed in vivid color directly on glass. Get 10% off your first order.
    • -
    • Change the Terms: White supremacists are using online platforms for hate. It's time to change the terms.
    • -
    ]]>
    ATP Our 300th Episode Spectacular! no02:23:54
    299: Headphone BumpMarco ArmentThu, 08 Nov 2018 01:00:19 +0000http://atp.fm/episodes/299513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5be1172a40ec9aef4fe3724a -
  • The ATP Store is back!! Place your orders until 15 November!
  • -
  • Follow-up: -
  • -
  • iPad Pro impressions
  • -
  • #askatp
      -
    • How has being a programmer changed our world view? (via Jonathan Bowling) -
    • -
    • Why do high-end iOS devices need so much less RAM than Macs? (via Benedikt Beckmann)
    • -
    -
  • -
  • Post-show: More iPad thoughts & John's homework laptop
  • - -

    Sponsored by:

    -
      -
    • Hover: Find a domain name for your passion. Get 10% off your first purchase.
    • -
    • Betterment: Outsmart average. Get up to 1 year managed free.
    • -
    • Marine Layer: Clothes that make it easy to get dressed in the morning. Use code atp for 15% off your first order.
    • -
    ]]>
    ATPHands-on impressions and info from Apple about the new iPad Pro.no02:02:58
    298: Protective SandwichMarco ArmentThu, 01 Nov 2018 15:22:32 +0000http://atp.fm/episodes/298513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5bd922e08985831ed03cf156 -
  • ATP T-shirts and pins are back!
  • -
  • Apple October 2018 event -
  • -
  • Post-show: Hobnobbing with the Apple executives
  • - -

    Sponsored by:

    -
      -
    • Casper: Get $50 off select mattresses with code ATP. Terms and conditions apply.
    • -
    • Eero: Finally, Wi-Fi that works. Get $100 off a Wi-Fi system and a year of eero Plus with code ATP.
    • -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    ]]>
    ATPAnalysis and hands-on coverage (!) of Apple's October event and the updated MacBook Air, Mac Mini, and iPad Pro.no02:26:44
    297: It Also Deserves to DieMarco ArmentThu, 25 Oct 2018 16:21:11 +0000http://atp.fm/episodes/297513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5bd1393f71c10b27a6774c08 -
  • Instagram stories and the dynamimcs of retweets -
  • -
  • Follow-up: -
  • -
  • The iPhone XR -
  • -
  • The forthcoming iPad event
  • -
  • #askatp -
  • -
  • Post-show: Casey's Waffle Woes -
  • - -

    Sponsored by:

    -
      -
    • Warby Parker: Glasses should not cost as much as an iPhone. Order your free Home Try-Ons today.
    • -
    • Linode: Instantly deploy and manage an SSD server in the Linode Cloud. Get a $20 credit with code atp2018.
    • -
    • AfterShokz: Headphones powered by bone-conduction technology. Get $50 off a Trekz Air bundle with code atpbundle.
    • -
    ]]>
    ATPiPhone XR reviews, why next week's Apple event is so exciting, why retweets ruined Twitter, and which of us is the Wafflemaster.no02:22:06
    296: Giant Buffers of FloatsMarco ArmentThu, 18 Oct 2018 15:10:44 +0000http://atp.fm/episodes/296513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5bc7fa541905f4be76361fb2 -
  • Pre-show: Marco's look-ahead limiter -
  • -
  • Apple fixes bagel emoji
  • -
  • Follow-up: -
  • -
  • Palm Phone
  • -
  • Photoshop CC on iPad -
  • -
  • #askatp -
  • -
  • Post-show: Dark patterns in Destiny
  • - -

    Sponsored by:

    -
      -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    • Fracture: Photos printed in vivid color directly on glass. Get a special discount with your first order.
    • -
    • Away: Travel smarter with the suitcase that charges your phone. Get $20 off a suitcase with code ATP.
    • -
    ]]>
    ATPAdventures in audio programming, fresh bagels, real Photoshop coming to iPad, and a new Palm phone — sort of.no02:13:21
    295: Outlets That SuckMarco ArmentThu, 11 Oct 2018 20:39:42 +0000http://atp.fm/episodes/295513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5bbebd9e652dea2c18579b49 -
  • Watch pontification -
  • -
  • Trading in with Apple GiveBack
  • -
  • Follow-up: -
  • -
  • Screen Time -
  • -
  • iCloud Family accounts
  • -
  • Apple Watch Walkie-Talkie -
  • -
  • Bloomberg's story on Chinese spy chips -
  • -
  • #askatp -
  • -
  • Post-show: RIP manual transmissions? -
  • - -

    Sponsored by:

    -
      -
    • Betterment: Outsmart average. Get up to 1 year managed free.
    • -
    • Molekule: The only air purifier that actually destroys pollutants.
    • -
    • Mack Weldon: Better than whatever you’re wearing right now. Get 20% off your first order with code ATP.
    • -
    ]]>
    ATPCasey's future Panerai won't have Walkie-Talkie mode, but he can always use his house's intercom system instead.no01:55:16
    294: The Intersection of Liberal Arts and WhateverMarco ArmentThu, 04 Oct 2018 16:04:52 +0000http://atp.fm/episodes/294513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5bb584ea1905f45e25301aba -
  • The abomination that is Apple's take on the bagel emoji -
  • -
  • iPhone 🎾 charging times
  • -
  • Follow-up:
      -
    • The A12 has a lot of cache (via Henrik Wannheden)
    • -
    • Chris Lattner weighs in on 64-bit watches and excuses
    • -
    • Apple Watch fall detection is only on by default for people at least 65 years old
    • -
    -
  • -
  • Critiquing others' work -
  • -
  • Mojave -
  • -
  • #askatp -
  • -
  • Post-show: Casey dabbles with a drone
  • - -

    Sponsored by:

    -
      -
    • Casper: Get $50 off select mattresses with code ATP. Terms and conditions apply.
    • -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    • Eero: Finally, Wi-Fi that works. Use code ATP for free overnight shipping to the US or Canada.
    • -
    ]]>
    ATPMojave, Marzipan, hostile work environments, and the bagel emoji.no01:53:20
    293: You Know My PouchesMarco ArmentThu, 27 Sep 2018 23:08:21 +0000http://atp.fm/episodes/293513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5bac4c2e104c7b4001088036 -
  • Please give some money to St. Jude. It literally helps save lives.
  • -
  • Follow-up: -
  • -
  • Our Marco & John's new toys -
  • -
  • Creative Selection by Ken Kocienda -
  • -
  • Post-show: Marco's ocean status
  • - -

    Sponsored by:

    -
      -
    • RXBAR: Whole food protein bars made with 100% real ingredients. Use code ATP for 25% off your first order.
    • -
    • Prime Video Channels: Great entertainment delivered to you instantly, anytime, anywhere.
    • -
    • Marine Layer: Clothes that make it easy to get dressed in the morning. Use code ATP for 15% off your first order.
    • -
    ]]>
    ATPOur reviews of the iPhone XS, Apple Watch Series 4, Creative Selection, and Scott Forstall.no02:10:12
    292: Start a Rotate Chicken Nuggets TimerMarco ArmentThu, 20 Sep 2018 20:38:28 +0000http://atp.fm/episodes/292513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5ba31335575d1fbc075a607e -
  • Please give some money to St. Jude. It literally helps save lives.
  • -
  • Follow-up: -
  • -
  • Underplayed improvements to iPhone XR/XS camera and Watch Series 4 -
  • -
  • No landscape home screens on XR/XS Max
  • -
  • What did we order? -
  • -
  • Testing HomePod's multiple named timers
  • -
  • Overcast 5 -
  • -
  • Post-show Casey on Cars: 2018 Volkswagen Golf GTI Autobahn -
  • - -

    Sponsored by:

    -
      -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    • Eero: Finally, Wi-Fi that works. Use code ATP for free overnight shipping to the US or Canada.
    • -
    • Casper: Get $50 off select mattresses with code ATP. Terms and conditions apply.
    • -
    ]]>
    ATPOvercast 5, HomePod timers, and learning more about iPhone XS and Apple Watch Series 4.no01:51:49
    291: John's Special DayMarco ArmentThu, 13 Sep 2018 18:57:12 +0000http://atp.fm/episodes/291513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5b99d94d575d1f3c47440092 -
  • Follow-up: -
  • -
  • Apple's September Event -
  • -
  • Post-show: John's special day
  • - -

    Sponsored by:

    -
      -
    • Molekule: The only air purifier that actually destroys pollutants.
    • -
    • Betterment: The smart way to manage your money. Get up to 1 year managed free.
    • -
    • AfterShokz: Headphones powered by bone-conduction technology. Use code atpbundle for $55 off a Trekz Air bundle.
    • -
    ]]>
    ATPApple Watch Series 4, iPhone XS, iPhone XS Max, iPhone XR, and another truly special event.no02:24:21
    290: A Weird SandwichMarco ArmentThu, 06 Sep 2018 20:43:59 +0000http://atp.fm/episodes/290513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5b909d8c562fa7cd99106d9c -
  • Follow-up: -
  • -
  • iPhone "XS" marketing image found -
  • -
  • Apple Watch "Series 4" marketing image found
  • -
  • #askatp:
      -
    • Getting started with backups (via JD Lewin)
    • -
    • Pre-ordering an unlocked iPhone (via the very handsome Stephen Kim)
    • -
    • What's the "cheapest" Apple product? -
    • -
    -
  • -
  • Post-show Neutral: Casey's walk of shame... but also pride.
  • - -

    Sponsored by:

    -
      -
    • Creative Selection: An inside account of Apple's creative process.
    • -
    • Casper: You can be sure of your purchase with Casper’s 100 night risk-free, sleep-on-it trial.
    • -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    ]]>
    ATPOur predictions for next week's iPhone event, something about HomeKit hubs, and a special post-show Neutral.no02:20:40
    289: Everybody's in the AllianceMarco ArmentThu, 30 Aug 2018 05:14:13 +0000http://atp.fm/episodes/289513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5b8762200e2e72525f158e2e -
  • Follow-up: -
  • -
  • The AirPort Express lives! -
  • -
  • The End of AirPort -
  • -
  • iPhone mockups preview -
  • -
  • #askatp: -
  • -
  • Post-show: -
  • - -

    Sponsored by:

    -
      -
    • Jamf Now: Start securing your business today by setting up your first 3 devices for free, forever.
    • -
    • Gray Langur: A once-in-a-lifetime, all-inclusive, 2-week exploration of one of the world’s least accessible, yet astonishingly forward-thinking countries.
    • -
    • Techmeme Ride Home: The day's tech news, every day at 5 PM, from Silicon Valley's most-read news source. 15 minutes and you're up to date.
    • -
    ]]>
    ATPDiving into cutting-edge technologies: coaxial cable, the AirPort Express, and printers.no02:10:42
    288: Rich Person's Raspberry PiMarco ArmentThu, 23 Aug 2018 17:47:26 +0000http://atp.fm/episodes/288513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5b7e267d2b6a2880434787e9 -
  • Follow-up:
      -
    • Photo books → PDF (FileExportExport Book as PDF → ☑️ Production PDF)
    • -
    • Active vs. Passive Thunderbolt 3 cables
    • -
    • Graphing Calculator vs. Grapher -
    • -
    -
  • -
  • Low-end Mac rumors -
  • -
  • #askatp: -
  • -
  • Post-show: John's 🐝
  • - -

    Sponsored by:

    -
      -
    • Linode: Get one of the fastest, most efficient SSD cloud servers for only $5/month. Use code atp2018 for a $20 credit.
    • -
    • Handy: The most reliable name in house cleaning. Get your first 3-hour cleaning for $39 when you sign up for a plan with code ATP.
    • -
    • Molekule: The only air purifier that actually destroys pollutants.
    • -
    ]]>
    ATPPredictions and hopes for the rumored Mac Mini and MacBook Air updates.no01:57:11
    287: Open-Faced Compliment SandwichMarco ArmentThu, 16 Aug 2018 17:39:14 +0000http://atp.fm/episodes/287513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5b74eb65898583f4b90da7c2 -
  • Follow-up:
      -
    • Model 3 software update for the right jog wheel -
    • -
    • Printing PDFs from Photos by ⌥-clicking the Buy Book button
    • -
    • Backblaze -
    • -
    • Dock auto-hide delay
        -
      • defaults write com.apple.dock autohide-time-modifier -int 0
      • -
      • defaults write com.apple.dock autohide-delay -int 0
      • -
      -
    • -
    -
  • -
  • Marco reviews his 2018 13" MacBook Pro -
  • -
  • Fall laptop predictions
  • -
  • #askatp: -
  • -
  • Post-show: 🐝 -
  • - -

    Sponsored by:

    -
      -
    • Eero: Finally, Wi-Fi that works. Use code ATP to make overnight shipping free to the US and Canada.
    • -
    • Away: Travel smarter with the suitcase that charges your phone. Get $20 off your suitcase with code ATP.
    • -
    • AfterShokz: Headphones powered by bone-conduction technology. Use code atpbundle for $55 off a Trekz Air bundle.
    • -
    ]]>
    ATP2018 13" MacBook Pro review, fall Mac speculation, and John's preparations for a war he intends to win.no02:08:14
    286: I Respect a Good CrustMarco ArmentThu, 09 Aug 2018 21:58:35 +0000http://atp.fm/episodes/286513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5b6bafdc562fa7f615300a00 -
  • Pre-show: John's pie-filled vacation -
  • -
  • Follow-up: -
  • -
  • Apple removes Infowars propoganda from its podcast directory -
  • -
  • #askatp: -
  • -
  • Post-show Neutral: Tesla screws up a good thing
  • - -

    Sponsored by:

    -
      -
    • Hover: Find a domain name for your passion. Get 10% off your first purchase.
    • -
    • Betterment: Rethink what your money can do. Get up to 1 year managed free.
    • -
    • Squarespace: Make your next move. Enter code ATP for 10% off your first order.
    • -
    ]]>
    ATPBreakfast pastries, large online backups, and de-listing from podcast directories.no01:56:16
    285: Do Not Drill Holes in This HouseMarco ArmentMon, 30 Jul 2018 15:27:00 +0000http://atp.fm/episodes/285513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5b5bde08562fa73ce922b7f8 -
  • Follow-up: -
  • -
  • On an infinite timescale, when does Apple move away from Intel? -
  • -
  • What happened to Apple's whimsy? -
  • -
  • #askatp: -
  • -
  • Post-show: Neutral -
  • - -

    Sponsored by:

    -
      -
    • Casper: Get $100 off the Casper Wave mattress with code atp100. Terms and conditions apply.
    • -
    • Squarespace: Make your next move. Get 10% off your first order with code ATP.
    • -
    • Simple Contacts: Renew your contact-lens prescription with our online vision test, and save $20 on your contacts with code ATP20.
    • -
    ]]>
    ATPHas Apple lost its whimsy? Has Casey found his next car? Has Marco's house made it through the episode?no02:02:25
    284: Hotel California KeyboardMarco ArmentThu, 26 Jul 2018 17:40:56 +0000http://atp.fm/episodes/284513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5b5939388a922d06e019c9b8 -
  • Follow-up: -
  • -
  • Corrections on the throttling discussion from last week -
  • -
  • VRM-throttling theory
  • -
  • Apple's official throttling fix -
  • -
  • Intel Power Gadget -
  • -
  • Intel and the Danger of Integration -
  • -
  • #askatp: -
  • -
  • Post-show: The Spanish Italian food inquisition -
  • - -

    Sponsored by:

    -
      -
    • AfterShokz: Headphones powered by bone-conduction technology. Use code atpbundle for $55 off a Trekz Air bundle.
    • -
    • Backblaze: Unlimited cloud backup for $5/month. Start protecting your data with a 15-day free trial today.
    • -
    • Linode: Get one of the fastest, most efficient SSD cloud servers for only $5/month. Use code atp2018 for a $20 credit.
    • -
    ]]>
    ATPModern CPUs are more complicated than tomato sauce.no02:06:22
    283: Just Lower Your StandardsMarco ArmentThu, 19 Jul 2018 15:53:42 +0000http://atp.fm/episodes/283513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5b50006a88251b6ef153a11d -
  • Pre-show: Videolog(ue) -
  • -
  • Follow-up: -
  • -
  • "Beware the Core i9" -
  • -
  • Apple ends photo printing operation
  • -
  • #askatp: -
  • -
  • Post-show Neutral -
  • - -

    Sponsored by:

    -]]>
    ATPHot MacBooks, cool early-'90s cars, and the rush to get the last Apple photo books.no01:50:21
    282: Not Unreliable EnoughMarco ArmentSun, 15 Jul 2018 15:23:01 +0000http://atp.fm/episodes/282513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5b4ac046758d462d95567d92 -
  • Follow-up: -
  • -
  • New MacBook Pros -
  • -
  • #askatp:
      -
    • Capturing out-of-warranty hardware (via Ali Shah)
    • -
    • How do I use a 34" monitor? (via Andrew Troup)
    • -
    • Lost picture troubleshooting for Brian Sturm
    • -
    -
  • -
  • Post-show: Let's talk about Europe. -
  • - -

    Sponsored by:

    -
      -
    • RXBAR: Whole food protein bars. Use code ATP for 25% off your first order and free shipping.
    • -
    • Mack Weldon: Better than whatever you’re wearing right now. Use code ATP for 20% off your first order.
    • -
    • Eero: Finally, Wi-Fi that works. Use code ATP to make overnight shipping free to the US and Canada.
    • -
    ]]>
    ATPDon't worry, we didn't just talk about the new MacBook Pro. We also had time to review all of Europe.no02:12:00
    281: My Private SiriMarco ArmentMon, 02 Jul 2018 15:50:34 +0000http://atp.fm/episodes/281513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5b39970bf950b774335d69ec -
  • Home repairs: Window air conditioners -
  • -
  • Computer repairs
      -
    • Casey repairs Erin's MacBook Air... maybe. -
    • -
    • Marco's iMac wants to be like Casey's
    • -
    -
  • -
  • Follow-up: -
  • -
  • Mojave LaunchServices can launch flat-style app bundles
  • -
  • Google Home impresses John
  • -
  • Apple Maps is getting a whole lotta love -
  • -
  • AirPod (and headphone!) rumors -
  • -
  • #askatp -
  • -
  • Post-show: Neutral
  • - -

    Sponsored by:

    -
      -
    • Betterment: Rethink what your money can do. Get up to 1 year managed free.
    • -
    • Squarespace: Make your next move. Visit http://squarespace.com/atp and enter code ATP for 10% off your first order.
    • -
    • Casper: For a limited time, save up to $225 off your order! This special offer expires July 9, 2018. Terms and conditions apply.
    • -
    ]]>
    ATPApple's upcoming Maps updates, a new Duplex demo, rumored new headphones, and air-conditioner optimizations.no01:47:54
    280: Extinction-Level EventMarco ArmentThu, 28 Jun 2018 17:23:58 +0000http://atp.fm/episodes/280513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5b3441ba575d1fa05d16bdda -
  • Follow-up: -
  • -
  • Apple launches butterfly-keyboard repair program -
  • -
  • Marco's new 🏖️💻 -
  • -
  • Marzipan -
  • -
  • #askatp -
  • -
  • Post-show: Casey had a falling out with (not of) the Jeep
  • - -

    Sponsored by:

    -
      -
    • Betterment: Rethink what your money can do. Get up to 1 year managed free.
    • -
    • Linode: Get one of the fastest, most efficient SSD cloud servers for only $5/month. Use code atp2018 for a $20 credit.
    • -
    • Away: Travel smarter with the suitcase that charges your phone. Get $20 off a suitcase with code ATP.
    • -
    ]]>
    ATPMarco's iMac survived its journey, Casey survived his borrowed Jeep, and John survived the WWDC meal plan.no02:04:31
    279: You’ll Hate What I’m Actually DoingMarco ArmentThu, 21 Jun 2018 21:52:20 +0000http://atp.fm/episodes/279513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5b2b12f11ae6cfe559022866 -
  • Accidental Test Kitchen: Induction cooktops
  • -
  • International cellular-data options -
  • -
  • Follow-up:
      -
    • Simulator cloning is fast because of APFS cloning
        -
      • cp -c
      • -
      -
    • -
    • Notarized apps and signing
    • -
    -
  • -
  • Marco's 🏖️ setup -
  • -
  • John's reload extension lives thanks to Isaac Halvorson
  • -
  • ATP appears in WWDC Session 501: Introducing Podcast Analytics! (~11 minutes) -
  • -
  • More on WWDC -
  • -
  • #askatp -
  • -
  • Post-show: "Casey's" Jeep -
  • - -

    Sponsored by:

    -
      -
    • Squarespace: Make your next move with a beautiful website. Use code ATP for 10% off your first order.
    • -
    • RXBAR: Whole-food protein bars. We tell you what’s on the inside on the outside. Use code ATP for 25% off your first order.
    • -
    • AfterShokz: Headphones powered by bone-conduction technology. Use code atp30 for $30 off the weightless, wireless Trekz Air.
    • -
    ]]>
    ATPInternational data plans, the final beach setup, wrapping up WWDC news, and the debut of Accidental Test Kitchen.no01:48:40
    278: Best Worst InfluenceMarco ArmentThu, 14 Jun 2018 18:35:40 +0000http://atp.fm/episodes/278513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5b21e06703ce6459ac4b80ac -
  • Follow-up: -
  • -
  • RIP, other chat networks in Messages.app (via Sam Gross)
  • -
  • APFS snapshotting and iOS Simulator parallel testing (via Renaud Leinhart)
  • -
  • How's the iOS 12 beta?
  • -
  • Stereo HomePod pair review -
  • -
  • More on iOS 12 -
  • -
  • macOS 10.14 Mojave: Possibly The John Siracusa Review -
  • -
  • #askatp -
  • -
  • Post-show: Casey quit his job -
  • - -

    Sponsored by:

    -
      -
    • Molekule: The only air purifier that actually destroys pollutants.
    • -
    • AfterShokz: Headphones powered by bone-conduction technology. Use code atp30 for $30 off the weightless, wireless Trekz Air.
    • -
    • Rover: The largest network of 5-star pet sitters and dog walkers in North America. Use code accidentaltech for $25 off your first booking.
    • -
    ]]>
    ATPMemoji, stereo HomePods, Mojave Dark Mode, Safari tracking prevention, and Casey putting his job in the parking lot.no02:03:53
    277: You're Not a Mac App YetMarco ArmentTue, 05 Jun 2018 22:29:42 +0000http://atp.fm/episodes/277513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5b170e158a922d6ca3918134We're live from WWDC 2018!

    - -

    Sponsored by:

    -
      -
    • Audible Careers: Join the company that is changing the world, one listener at a time.
    • -
    • AfterShokz: Headphones powered by bone-conduction technology. Get $30 off the weightless, wireless Trekz Air with code atp30.
    • -
    • Microsoft Azure: Build intelligent iOS apps that scale.
    • -
    ]]>
    ATPLive from WWDC 2018!no01:32:41
    276: Observing a Black HoleMarco ArmentThu, 31 May 2018 15:04:33 +0000http://atp.fm/episodes/276513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5b0f6bb28a922dfc86c78bb4 -
  • App Camp for Girls fundraiser: Live Near WWDC
  • -
  • iOS 11.4
      -
    • Messages in the Cloud: Settings → {Your Name} → iCloudMessages
    • -
    -
  • -
  • WWDC -
  • -
  • Marco's 🏖️💻 problem -
  • -
  • #askatp -
  • -
  • Post-show Neutral: Casey is a sad panda
  • - -

    Sponsored by:

    -
      -
    • Casper: Get $100 off your Wave purchase with code atp100. Terms and conditions apply.
    • -
    • Eero: Finally, Wi-Fi, that works. Use code ATP for free overnight shipping to the US and Canada.
    • -
    • Squarespace: Make your next move with a beautiful website. Use code ATP for 10% off your first order.
    • -
    ]]>
    ATPiOS 11.4, what we hope to see at WWDC, Marco's beach problem, and Casey's car problem.no02:14:33
    275: It's Difficult When You’re a BillionaireMarco ArmentThu, 24 May 2018 22:21:28 +0000http://atp.fm/episodes/275513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5b062e00f950b7cb32c519fe -
  • Follow-up: -
  • -
  • Tesla and Elon Musk -
  • -
  • Marco is dabbling with Swift (‼️) -
  • -
  • #askatp -
  • -
  • Post-show Neutral: Marco drove something ridiculous
  • - -

    Sponsored by:

    -
      -
    • Linode: Get one of the fastest, most efficient SSD cloud servers starting at just $5/month. Use code atp2018 for a $20 credit.
    • -
    • Lambda School: Register now for iOS Development 101, a free introduction to iOS development.
    • -
    • AfterShokz: Headphones powered by bone-conduction technology. Get $30 off the weightless, wireless Trekz Air with code atp30.
    • -
    ]]>
    ATPChanged minds, old ears, giving in to Swift, and whether Elon Musk is a net positive.no01:53:17
    274: Not the Teddy Bear's FaultMarco ArmentThu, 17 May 2018 20:59:42 +0000http://atp.fm/episodes/274513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5afcf20e758d467e132612d5 -
  • Yanny vs. Laurel -
  • -
  • Follow-up: -
  • -
  • Amazon and executive diversity
  • -
  • 🤬 Twitter. -
  • -
  • Google I/O -
  • -
  • Apple Keyboard class action lawsuit -
  • -
  • Microsoft Surface Hub 2
  • -
  • #askatp -
  • -
  • Post-show: Casey's Car Corner -
  • - -

    Sponsored by:

    -
      -
    • Betterment: Rethink what your money can do.
    • -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    • AfterShokz: Use code atp30 for $30 off the weightless, wireless Trekz Air.
    • -
    ]]>
    ATPMisheard names, big tables, Twitter's antics, class-actions, and more from Google I/O.no01:59:09
    273: Playing You Like a Video GameMarco ArmentThu, 10 May 2018 14:37:30 +0000http://atp.fm/episodes/273513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5af3b67f70a6ad79876066a7 -
  • Follow-up: Disabling "Raise to Listen" on iPhone X
  • -
  • You should go to Layers
  • -
  • Jony Ive interviewed by Hodinkee -
  • -
  • Google Duplex -
  • -
  • 20 years of USB -
  • -
  • #askatp -
  • - -

    Sponsored by:

    -
      -
    • Mack Weldon: Reinventing men’s basics with smart design, premium fabrics, and simple shopping. Get 20% off your first order with code ATP.
    • -
    • Eero: Finally, Wi-Fi, that works. Use code ATP for free overnight shipping to the US and Canada.
    • -
    • Casper: Get $100 off your Wave purchase with code atp100. Terms and conditions apply.
    • -
    ]]>
    ATPHello, um, do you have any time to listen to our podcast today between 10am and 12pm, mmm?no01:38:49
    272: 60% SatisfiedMarco ArmentTue, 01 May 2018 05:05:01 +0000http://atp.fm/episodes/272513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5ae7e137352f53431722ce77 -
  • ATP MERCHANDISE!!
      -
    • Get your clothing by May 7 @ 9 PM ATP time
    • -
    • Get your pins NOW! They're about to sell out!
    • -
    -
  • -
  • Follow-up:
      -
    • More on 32-bit deprecation
    • -
    • Raise to Listen and phantom audio messages on iPhone X -
    • -
    -
  • -
  • More about Marzipan
  • -
  • What is left for WWDC?
  • -
  • Apple's rumored AR glasses
  • -
  • Overcast 4.2: The privacy update -
  • -
  • #askatp
      -
    • Apple IDs at work (via Alex Faber)
    • -
    • Camera paranoia (via Ryan Taylor) -
    • -
    • CarPlay thoughts (via John)
    • -
    -
  • - -

    Sponsored by:

    -
      -
    • Hover: Find a domain name for your passion. Get 10% off your first purchase.
    • -
    • Squarespace: Make your next move with a beautiful website. Enter code ATP for 10% off your first order.
    • -
    • Rover: Rover is the largest network of 5-star pet sitters and dog walkers in North America.
    • -
    ]]>
    ATPStale Marzipan, prescription AR sunglasses, and protecting listeners from tracking pixels.no01:55:05
    271: Voting With Your Virtual FeetMarco ArmentThu, 26 Apr 2018 16:11:11 +0000http://atp.fm/episodes/271513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5ae13da9758d46b32eda65ac -
  • ATP MERCHANDISE!!
      -
    • Get your clothing by May 7, 9 PM ATP time
    • -
    • Get your pins ASAP! They will sell out!
    • -
    • Sorry, no flamethrowers.
    • -
    -
  • -
  • Follow-up: -
  • -
  • NOT A TOUCH SCREEN
  • -
  • Turns out™, Siri sucks
  • -
  • iPhone X Mid-Cycle Performance Review
  • -
  • Safari dark mode?
  • -
  • #askatp
      -
    • What does Apple gain by ending 32-bit support? (via Jacob Ford)
    • -
    • 🙄 Destiny talk 🙄 (blame Juho Leinonen)
    • -
    • What used Wrangler or convertible would we buy with $15k for weekend fun? (via Mike)
    • -
    -
  • -
  • Post-show Neutral: Casey's moving violations
  • - -

    Sponsored by:

    -
      -
    • Eero: Finally, Wi-Fi, that works. Use code ATP for free overnight shipping to the US and Canada.
    • -
    • Linode: Fast SSD cloud servers starting at just $5/month. Use code atp2018 for a $20 credit.
    • -
    • RXBAR: Whole food protein bars made with 100% real ingredients. Use code ATP for 25% off your first order.
    • -
    ]]>
    ATPBitcode, backpacks, untouchable screens, and bringing the iPhone X into our office for a little chat. Please close the door behind you.no01:35:21
    270: Three Major ZippersMarco ArmentWed, 18 Apr 2018 14:52:19 +0000http://atp.fm/episodes/270513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5ad16d1af950b77bff3ce324 -
  • Backpacks: -
  • -
  • HomePod sales look bad
  • -
  • CloudFlare goes ARM
  • -
  • When was Apple's heyday? -
  • -
  • #askatp -
  • -
  • Post-show: Topics we don't feel we need to cover
  • - -

    Sponsored by:

    -]]>
    ATPWhen was Apple's heyday?no01:36:49
    269: Cooled by JellyfishMarco ArmentFri, 13 Apr 2018 01:33:52 +0000http://atp.fm/episodes/269513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5acecc37758d4676cbd3b751 -
  • Follow-up: -
  • -
  • The Mac Pro and Apple's Pro Workflow Team -
  • -
  • #askatp -
  • -
  • Post-show -
  • - -

    Sponsored by:

    -
      -
    • Molekule: The only air purifier that actually destroys pollutants.
    • -
    • Betterment: Rethink what your money can do. Get up to 1 year managed free.
    • -
    • Hello Fresh: A meal-kit delivery service so you can just cook, eat, and enjoy. Use code ATP30 for $30 off your first week.
    • -
    ]]>
    ATPWe invited pros to come on campus and join our Pro Listening Team to help us shape the next episode of our flagship podcast.no01:45:23
    268: A Tarnished Brass AgeMarco ArmentThu, 05 Apr 2018 16:47:26 +0000http://atp.fm/episodes/268513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5ac592bd575d1ffe219f27fa -
  • Follow-up: -
  • -
  • iCloud's free storage
  • -
  • Classroom for Mac: What does it mean?
  • -
  • Inductive charging is great. Who knew‽ -
  • -
  • Apple to use its own CPUs in Macs? -
  • -
  • #askatp -
  • -
  • Post-show Neutral: -
  • - -

    Sponsored by:

    -
      -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    • Casper: Get $100 off the Casper Wave mattress with promo code atp100. Terms and conditions apply.
    • -
    ]]>
    ATPARM Macs, education feedback, expensive car-phone mounts, The San José Show, subscription BMWs, and ginger tea.no01:57:54
    267: I Slowly Ate the CrystalsMarco ArmentSat, 31 Mar 2018 19:07:36 +0000http://atp.fm/episodes/267513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5abefb168a922d9f461e9f44 -
  • Pre-show: Marco's road trip -
  • -
  • Follow-up: -
  • -
  • ATP Live at WWDC
  • -
  • Walkie talkies for tandem road trips
  • -
  • Apple's education event -
  • -
  • The new 9.7" iPad -
  • -
  • #askatp -
  • -
  • Post-show: Impossible feats of packing by "Underscore" David Smith
  • - -

    Sponsored by:

    -
      -
    • Away: Travel smarter with the suitcase that charges your phone. Get $20 off any suitcase with code ATP.
    • -
    • AfterShokz: Headphones powered by bone-conduction technology. Get $30 off the Trekz Air with code ATP30.
    • -
    • JAMF Now: Set up and protect your Apple devices at work, from anywhere. First 3 devices are free.
    • -
    ]]>
    ATPApple's role in education, the new 9.7" iPad, best-value Apple products, walkie talkies in 2018, and impossible feats of packing.no01:36:19
    266: Text Adventure ModeMarco ArmentFri, 23 Mar 2018 13:43:39 +0000http://atp.fm/episodes/266513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5ab32452758d46f90e1ec51b -
  • Follow-up: -
  • -
  • This keyboard feels so real, mannnnnn...
  • -
  • Apple is developing its own display tech -
  • -
  • #askatp -
  • -
  • Post-show Neutral -
  • - -

    Sponsored by:

    -
      -
    • Instabug: A lightweight mobile SDK for comprehensive bug and crash reporting and intuitive user feedback. Use code ATP for 20% off all plans.
    • -
    • Betterment: Rethink what your money can do. Get up to 1 year managed free.
    • -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    ]]>
    ATPCylinder syntax, GDPR, simulated keyboards, MicroLED ambitions, game-console "bits", sports fandom, and why it's hard to drop Facebook.no02:09:44
    265: Simon Says Volume FiveMarco ArmentThu, 15 Mar 2018 22:15:57 +0000http://atp.fm/episodes/265513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5aa9e5f0f9619ada928bd662 -
  • Follow-up: -
  • -
  • Alexa can solicit follow-up
  • -
  • Siri drama at The Information -
  • -
  • Crumb-proof keyboards
  • -
  • MacBook Air refresh? -
  • -
  • MacBook fantasies -
  • -
  • WWDC 2018 announced -
  • -
  • Apple acquires Texture -
  • -
  • #askatp
      -
    • Migrations between Macs (via Matt Wallin)
    • -
    • Polite language in user interfaces (via Bill Balinor) -
    • -
    • Video game completionists (via Chad Toporski)
    • -
    -
  • -
  • Post-show: Do working developers need to know the "fundamentals"? -
  • - -

    Sponsored by:

    -
      -
    • Jamf Now: Set up and protect your Apple devices at work, from anywhere. First 3 devices are free.
    • -
    • AfterShokz: Headphones powered by bone-conduction technology. Get $30 off your Trekz Air with code ATP30.
    • -
    • Backblaze: Online backup for $5/month. Native. Unlimited. Unthrottled. Uncomplicated.
    • -
    ]]>
    ATPIf you could add just one more port to your new, water-resistant laptop, what would it be?no02:07:33
    264: Every Building Has BugsMarco ArmentWed, 07 Mar 2018 21:49:50 +0000http://atp.fm/episodes/264513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5a9f66cf652dea8c73aa8cef -
  • Follow-up: -
  • -
  • Onboard all the things -
  • -
  • Apple Park is hazardous
  • -
  • #askatp -
  • -
  • Post-show Neutral: What car would we pick for our other hosts?
  • - -

    Sponsored by:

    -
      -
    • Betterment: Rethink what your money can do.
    • -
    • Linode: Get one of the fastest, most efficient SSD cloud servers for only $5/month. Use code atp2018 for a $20 credit.
    • -
    • Hello Fresh: A meal-kit delivery service that shops, plans, and delivers your favorite step-by-step recipes and pre- measured ingredients. Get $30 off your first week with code ATP30.
    • -
    ]]>
    ATPJoin us for an out-of-box experience. Just watch out for the walls.no01:51:35
    263: Old PotatoMarco ArmentThu, 01 Mar 2018 21:52:12 +0000http://atp.fm/episodes/263513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5a97793124a6940809b7bd86 -
  • Follow-up: -
  • -
  • Casey adopts a new child -
  • -
  • AOL Instant Messenger handles -
  • -
  • Vulkan is coming to macOS -
  • -
  • Apple's dual-display patent -
  • -
  • #asktiff -
  • -
  • #askatp -
  • -
  • Post-show: Playing for Fun -
  • - -

    Guest-starring Tiffany Arment!

    -

    Sponsored by:

    -
      -
    • Casper: Experience Casper’s most innovative mattress in your own home for 100 nights, risk-free. Use code atp100 for $100 off your Casper Wave.
    • -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    • Fracture: Photos printed in vivid color directly on glass. Get 15% off your first order with code ATP17.
    • -
    ]]>
    ATPCasey's AIM handle was so much worse than yours.no01:34:24
    262: A Clear Path to OkaynessMarco ArmentSat, 24 Feb 2018 17:56:32 +0000http://atp.fm/episodes/262513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5a90e5ec24a69489a7bfb430 -
  • Casey's new career of missing obvious programming solutions -
  • -
  • Siri all up in Daniel Jalkut's business
  • -
  • Sonos and wood
  • -
  • Who has a HomePod? -
  • -
  • Doubting Apple -
  • -
  • Siri -
  • -
  • Apple pumps the brakes -
  • -
  • #askatp -
  • -
  • Post-show: Post-show Neutral: Car buying challenge -
  • - -

    Sponsored by:

    -
      -
    • Betterment: Rethink what your money can do.
    • -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    • Simple Contacts: Renew your contact-lens prescription with our online vision test, and save $30 on your contacts with code ATP.
    • -
    ]]>
    ATPOne of us finally got a HomePod, another has some thoughts on Siri, and the third is waffling on the purchase as usual.no01:53:07
    261: Seven-Dollar Plastic GarbageMarco ArmentFri, 16 Feb 2018 16:38:54 +0000http://atp.fm/episodes/261513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5a8509178165f5ac7ea36545 -
  • Pre-show: Shake Shack, Five Guys, and Cookout
  • -
  • Follow-up:
      -
    • Battery feedback: 1, 2, 3
    • -
    • Casey's boneheaded mistake
    • -
    • Marco's grocery-list apps -
    • -
    -
  • -
  • HomePods put a ring on it -
  • -
  • Waze -
  • -
  • #askatp
      -
    • Are advanced safety features in cars comparable across marques?
    • -
    • What's the deal with Leica cameras? -
    • -
    -
  • -
  • Post-show: Parenting
  • - -

    Sponsored by:

    -
      -
    • Away: Travel smarter with the suitcase that charges your phone. Get $20 off any suitcase with code ATP.
    • -
    • Linode: Get one of the fastest, most efficient SSD cloud servers for only $10/month. Use code atp2018 for a $20 credit.
    • -
    • Eero: Finally, Wi-Fi that works. Use code ATP to make overnight shipping free.
    • -
    ]]>
    ATPSound quality, HomePod rings, hamburgers, parenting, Waze, and Leica. No controversy here.no01:48:29
    260: The Todoyist ProblemMarco ArmentFri, 09 Feb 2018 01:01:41 +0000http://atp.fm/episodes/260513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5a7bd004f9619af8947147a3 -
  • Follow-up:
      -
    • Is throttling old phones really better?
    • -
    • Does anyone other than tech bloggers give a 💩 about smart speakers?
    • -
    • Raspberri Pi music player update -
    • -
    -
  • -
  • Apple's emoji is theirs and you can't have it. -
  • -
  • Marco's to-do app spirit walk -
  • -
  • #askatp -
  • -
  • Post-show: -
  • - -

    Sponsored by:

    -
      -
    • Betterment: Rethink what your money can do.
    • -
    • Casper: Take advantage of the limited-time President's Day offer for the first time ever!
    • -
    • Hello Fresh: A meal-kit delivery service that shops, plans, and delivers your favorite step-by-step recipes and pre-measured ingredients. Get $30 off your first week with code ATP30.
    • -
    ]]>
    ATPApp rejections for emoji, choosing a to-do app, and boosting spirits with an occasional spirit.no01:56:10
    259: I Hired MyselfMarco ArmentThu, 01 Feb 2018 20:34:06 +0000http://atp.fm/episodes/259513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5a7299f8c8302573ec2a9d16 -
  • Who ordered a HomePod? -
  • -
  • Casey orders an Ethernet adapter -
  • -
  • Casey's Mac "app" -
  • -
  • Marco's secret project -
  • -
  • #askatp -
  • -
  • Post-show: Casey on Cars Episode 2: Alfa Romeo Stelvio Ti -
  • - -

    Sponsored by:

    -
      -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    • Fracture: Photos printed in vivid color directly on glass. Get 10% off your first order.
    • -
    • Hover: Get 10% off any domain name from Hover — extensions for anything you’re passionate about.
    • -
    ]]>
    ATPCasey makes an app and tells a story, Marco makes a gadget to reduce convenience, and all three of us fail to perform our basic duty as Apple podcasters.no02:11:39
    258: Non-Beta DataMarco ArmentThu, 25 Jan 2018 21:52:51 +0000http://atp.fm/episodes/258513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5a695479ec212d357d7ab3fc -
  • Follow up: Battery replacement reticence
  • -
  • iOS 11.3 battery health messaging
  • -
  • macOS Spectre and Meltdown fixes go back to El Capitan
  • -
  • HomePod preorders and ship date announced -
  • -
  • iOS 11.3 preview -
  • -
  • tvOS 11.3
  • -
  • macOS 10.13.4 and the end of 32-bit
  • -
  • #askatp -
  • -
  • Post-show: BMW are money-grubbing jerks -
  • - -

    Sponsored by:

    -
      -
    • SRC, Inc: SRC is hiring digital, software, systems and test engineers. Explore your career options today.
    • -
    • Betterment: Rethink what your money can do.
    • -
    • Casper: Get $50 toward select mattresses with code ATP. Terms and conditions apply.
    • -
    ]]>
    ATPHomePod's imminent release and the previews of iOS 11.3 and macOS 10.13.4.no01:35:30
    257: Smell the WindMarco ArmentThu, 18 Jan 2018 19:58:28 +0000http://atp.fm/episodes/257513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5a601944652deae65fabc1b0 -
  • Casey and Erin had a baby! -
  • -
  • Casey's photo management workflow
  • -
  • Follow-up: Europe's black Mac
  • -
  • PCalc About screen, revver of fans
  • -
  • ARM Windows PCs -
  • -
  • Windows 10 Sets -
  • -
  • #askatp -
  • -
  • Post-show: A gift for Casey
  • - -

    Sponsored by:

    -
      -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    • Hullo Pillow: Your favorite pillow, guaranteed.
    • -
    • RXBAR: Whole-food protein bars. We tell you what’s on the inside on the outside. Use code ATP for 25% off your first order.
    • -
    ]]>
    ATPBaby Liss II, photo management scripts, what ARM PCs and Windows Sets could mean for the evolution of the Mac, and a special gift for Casey.no01:35:34
    256: A Heroic Threshold for PainMarco ArmentThu, 11 Jan 2018 15:55:09 +0000http://atp.fm/episodes/256513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5a5593c153450a0fe1b2c6b5 -
  • Apple's Meltdown/Spectre statement -
  • -
  • iMac Pro peripherals on eBay -
  • -
  • Follow-up: Arctic Silver
  • -
  • Follow-up: Battery replacements and solving customer problems
  • -
  • Follow-up: iPhone X screen burn-in
  • -
  • Marco's iMac Pro, one week in
  • -
  • Sony α7R III review -
  • -
  • #askatp
      -
    • Would apple delay the Mac Pro for a new CPU architecture? (via Clint)
    • -
    • What does root do that sudo doesn't? (via Bryson)
    • -
    • Sonos One w/ Alexa? (via Jim Andeway)
    • -
    -
  • -
  • Post-show: -
  • - -

    Sponsored by:

    -
      -
    • Betterment: Rethink what your money can do.
    • -
    • Linode: Get one of the fastest, most efficient SSD cloud servers for only $10/month. Use code atp2018 for a $10 credit.
    • -
    • Fracture: Photos printed in vivid color directly on glass. Get 15% off your first order with code ATP17.
    • -
    ]]>
    ATPAn exploration of all seventeen of Casey's diversion kryptonites.no02:01:16
    255: The Thermal Paste LotteryMarco ArmentThu, 04 Jan 2018 22:35:42 +0000http://atp.fm/episodes/255513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5a4db20753450aa56e22e7ec -
  • Apple's battery slowdown PR -
  • -
  • iMac Pro thermal throttling
  • -
  • Marco's iMac Pro -
  • -
  • Intel CPU exploit -
  • -
  • #askatp -
  • -
  • Post-show: Marco stages an intervention for Casey -
  • - -

    Sponsored by:

    -
      -
    • FlightLogger: Real-time flight tracking for worry-free travels. Available for iOS and Android.
    • -
    • Casper: Start sleeping ahead of the curve. Get $50 toward select mattresses with code ATP.
    • -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    ]]>
    ATPCPU exploits, iMac Pro impressions, iPhone battery PR, and a special intervention for Casey.no02:05:04
    254: Hot Box With KnobsMarco ArmentTue, 26 Dec 2017 13:16:07 +0000http://atp.fm/episodes/254513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5a3b3c7e9140b77c67c46af4 -
  • Follow up: -
  • -
  • #askatp -
  • -
  • iPhone Performance and Battery Age -
  • -
  • iOS and macOS apps as one? -
  • -
  • Post-show: The Grand Tour season 2 review (so far) -
  • - -

    Sponsored by:

    -
      -
    • Casper: Start sleeping ahead of the curve. Get $50 toward any mattress purchase with code ATP.
    • -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    • Hello Fresh: A meal-kit delivery service so you can just cook, eat, and enjoy. Get $30 off your first week with code ATP30.
    • -
    ]]>
    ATPiPhone slowdowns with old batteries, a unified iOS/macOS UI framework, and padded walls.no
    253: I Think I Ordered OneMarco ArmentWed, 20 Dec 2017 15:18:58 +0000http://atp.fm/episodes/253513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5a3892ad9140b787273bba92 -
  • Follow up: -
  • -
  • #askatp -
  • -
  • NVIDIA vs. AMD GPUs in Macs -
  • -
  • More about the iMac Pro -
  • -
  • Post-show Analog(ue): Marco reflects on his interesting phone call -
  • - -

    Sponsored by:

    -
      -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    • Backblaze: Online backup for $5/month. Native. Unlimited. Unthrottled. Uncomplicated.
    • -
    • AfterShokz: Headphones powered by bone-conduction technology.
    • -
    ]]>
    ATPNVIDIA in Macs, fancy headphones, and a lot more about the iMac Pro.no02:05:51
    252: Any Day Could Be Mac Pro DayMarco ArmentThu, 14 Dec 2017 21:49:19 +0000http://atp.fm/episodes/252513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5a31ff380d9297f7991bdc57 -
  • Pre-show Neutral: Casey has car problems
  • -
  • Follow up: Magic Keyboard with Numeric Keypad is apparently bendy
  • -
  • #askatp -
  • -
  • John's black screen of death -
  • -
  • iMac Pro -
  • -
  • Post-show: More iMac Pro -
  • - -

    Sponsored by:

    -
      -
    • Betterment: Rethink what your money can do.
    • -
    • Hover: Find a domain name for your passion. Get 10% off your first purchase.
    • -
    • Fracture: Photos printed in vivid color directly on glass. Get 15% off your first order with code ATP17.
    • -
    ]]>
    ATPIn case you haven't heard yet, the iMac Pro was released.no01:56:27
    251: Uninstall Your Water Reminder App!Marco ArmentThu, 07 Dec 2017 21:45:17 +0000http://atp.fm/episodes/251513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5a28c39771c10b4cf9c38900 -
  • Casey argues for day/month/year instead of year/month/day (ISO 8601) -
  • -
  • Follow up:
      -
    • Root bug post was in the dev forums, not the support forums.
    • -
    • Reporting to product-security@apple.com
    • -
    • XProtect and Xplorer -
    • -
    • iOS keyboard woes and Gboard
    • -
    -
  • -
  • John's Touch Bar switch -
  • -
  • Jony Ive hears your feedback
  • -
  • #askatp
      -
    • Should we get rid of paper books? (via Josh Keegan)
    • -
    • Will Apple ever make backlit desktop keyboards? (via Craig)
    • -
    • How does dynamic podcast-ad targeting work? (via Prodan Statev)
    • -
    -
  • -
  • iOS 11.2 emergency release for December 2 bug
  • -
  • "Month 13 is out of bounds" bug in High Sierra
  • -
  • John's phone is full -
  • -
  • Post-show: Forecast -
  • - -

    Sponsored by:

    -
      -
    • Fracture: Photos printed in vivid color directly on glass. Get 10% off your first order with code ATP17.
    • -
    • AfterShokz: Headphones powered by bone-conduction technology.
    • -
    • Linode: Get one of the fastest, most efficient SSD cloud servers for only $10/month. Use code AccidentalPodcast10 for a $10 credit.
    • -
    ]]>
    ATPApple's big bugs, Casey's date formats, John's Touch Bar, and Marco's new Forecast app.no02:18:58251: Uninstall Your Water Reminder App!251: Uninstall Your Water Reminder App!
    250: We're Doing a ShowMarco ArmentThu, 30 Nov 2017 22:06:51 +0000http://atp.fm/episodes/250513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5a1f8b82c830253d32d60e7f -
  • Keyboard imprints on laptop screens -
  • -
  • Marco is no longer case-liss -
  • -
  • Follow-up: Fingerprint gloves 1 2
  • -
  • Thanksgiving follow-up: What about the Nintendo Switch? -
  • -
  • #askatp -
  • -
  • High Sierra "root" security issue -
  • -
  • Post-show: -
  • - -

    Sponsored by:

    -
      -
    • Hover: Find a domain name for your passion. Get 10% off your first purchase.
    • -
    • Away: Travel smarter with the suitcase that charges your phone. Get $20 off a suitcase with code ATP.
    • -
    • Eero: Finally, Wi-Fi that works.
    • -
    ]]>
    ATPOur 250th Episode Spectacular!no02:29:15250: We’re Doing a Show250: We’re Doing a Show
    249: Beehives Full of BeesMarco ArmentTue, 21 Nov 2017 20:31:11 +0000http://atp.fm/episodes/249513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5a13b04771c10bfdba632fe0 -
  • Follow up: -
  • -
  • HomePod gets delayed -
  • -
  • Regarding conspiracy theories (#caseywasright) -
  • -
  • #askatp -
  • -
  • Tech Thanksgiving -
  • -
  • Casey's iPhone X Problems -
  • -
  • Marco's best laptop ever -
  • -
  • Post-show Neutral: Tesla Roadster -
  • - -

    Sponsored by:

    -
      -
    • Betterment: Rethink what your money can do.
    • -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    • Simple Contacts: Renew your contact-lens prescription with our online vision test, and save $30 on your contacts with code ATP.
    • -
    ]]>
    ATPTech we're thankful for, the context of white cars, Marco's ancient new laptop, and the Tesla Roadster.no02:24:13249: Beehives Full of Bees249: Beehives Full of Bees
    248: Chili CookoffMarco ArmentThu, 16 Nov 2017 06:01:10 +0000http://atp.fm/episodes/248513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5a0d0e5ce4966bbd23602ddc -
  • Follow up:
      -
    • Tradespeople and Face ID
    • -
    • Multitasking gesture wonkiness on the home screen
    • -
    • "I got this phone for $50 and it's fine"
    • -
    • Pro-rated AppleCare+
    • -
    • MacBook key repairs
    • -
    -
  • -
  • OLED and burn-in -
  • -
  • iPhone X camera -
  • -
  • #askatp -
  • -
  • Qi chargers -
  • - -

    Sponsored by:

    -
      -
    • Jamf Now: Set up and protect your Apple devices at work, from anywhere. First 3 devices are free.
    • -
    • Betterment: Rethink what your money can do.
    • -
    • Fracture: Photos printed in vivid color directly on glass. Get 15% off your first order with code ATP17.
    • -
    ]]>
    ATPAfter covering burn-in, Portrait Mode, VPNs, and Qi chargers, we're still friends. Probably.no01:53:53248: Chili Cookoff248: Chili Cookoff
    247: A Place to Stick Your GumMarco ArmentFri, 10 Nov 2017 16:14:10 +0000http://atp.fm/episodes/247513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5a05327e24a694dfd62b7dd6 -
  • Follow up: -
  • -
  • App Camp for Girls fundraiser
  • -
  • #askatp -
  • -
  • iPhone X: The ATP Review -
  • -
  • Post-show: Casey's odd in-flight activities
  • - -

    Sponsored by:

    -
      -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    • Warby Parker: Boutique-quality, vintage-inspired glasses at a revolutionary price. Try up to five pairs at home for free.
    • -
    • Fracture: Photos printed in vivid color directly on glass. Get 10% off your first order with code ATP17.
    • -
    ]]>
    ATPiPhone X: The ATP Reviewno02:34:47247: A Place to Stick Your Gum247: A Place to Stick Your Gum
    246: I Just Want My StoriesMarco ArmentThu, 02 Nov 2017 21:36:41 +0000http://atp.fm/episodes/246513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:59fa95b19140b7ed47a8e0df -
  • Follow up: F1 flappy paddles
  • -
  • iPhone X preorder results
  • -
  • Recent performance of the rumor mill
  • -
  • Marco's iMac returns
  • -
  • MacBook Pro keyboard repair cost
  • -
  • Apple TV output formats -
  • -
  • #askatp -
  • -
  • iPhone X reviews, and the PR strategy -
  • -
  • Post-show: Casey the video editor -
  • - -

    Sponsored by:

    -
      -
    • Casper: You can be sure of your purchase with Casper’s 100-night risk-free, sleep-on-it trial.
    • -
    • Betterment: Rethink what your money can do.
    • -
    • Jamf Now: Set up and protect your Apple devices at work, from anywhere. First 3 devices are free.
    • -
    ]]>
    ATPiPhone X preorders and reviews, rumor mill report card, repair costs, and Casey's ascent into video.no02:21:38
    245: Senior-Citizen AdvantageMarco ArmentThu, 26 Oct 2017 21:16:09 +0000http://atp.fm/episodes/245513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:59f15788a8b2b0999be90da1 -
  • Follow up: The right to chair arms
  • -
  • #askatp -
  • -
  • It's preorder time -
  • -
  • What are the iPhone X's biggest risks?
  • -
  • The Mac Mini is... still a thing? -
  • -
  • Post-show: Casey's still a car journalist -
  • - -

    Sponsored by:

    -
      -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    • Fracture: Photos printed in vivid color directly on glass. Get 10% off your first order.
    • -
    • AfterShokz: Headphones powered by bone-conduction technology.
    • -
    ]]>
    ATPChair arms, Bitcoin, Phish, iPhone X orders, and the Mac Mini.no02:15:02
    244: The Mac RenaissanceMarco ArmentThu, 19 Oct 2017 22:31:15 +0000http://atp.fm/episodes/244513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:59e8154b8a02c7a7ecb06fdb -
  • Pre-show: ATP 243 notes
  • -
  • Follow up: -
  • -
  • #askatp -
  • -
  • Marco's MacBook Pro
  • -
  • MacBook keyboard failures
  • -
  • Somehow Casey got backed into a ton of Mac Pro talk (🙄 –Ed.)
  • -
  • Special ending theme by Jonathan Mann! Support Jonathan's work: -
  • -
  • Post-show: The VBR experiment -
  • - -

    Sponsored by:

    -
      -
    • Betterment: Rethink what your money can do.
    • -
    • Hover: Find a domain name for your passion. Get 10% off your first purchase.
    • -
    • Away: Travel smarter with the suitcase that charges your phone. Get $20 off with code ATP.
    • -
    ]]>
    ATPHow many iMac Pros will Marco buy before the Mac Pro comes out?no01:54:25
    243: Knew About the Dollar SignsMarco ArmentThu, 12 Oct 2017 20:42:31 +0000http://atp.fm/episodes/243513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:59ded87fd55b41672e6cf088 -
  • Pre-show: macOS upgrades vs. clean installs
  • -
  • Follow up: -
  • -
  • #askatp -
  • -
  • Apple Phone → Watch notifications -
  • -
  • Twitterrific for Mac
  • -
  • Apple TV gaming and Sky -
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show Neutral: Casey has pipe dreams about cars
  • - -

    Sponsored by:

    -
      -
    • Audible: Get a free audiobook with a 30-day trial.
    • -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    • Fracture: Photos printed in vivid color directly on glass. Get 10% off your first order.
    • -
    ]]>
    ATPWindow layouts, menu bars, clean installs, notification mysteries, and Apple TV gaming.no01:42:52
    242: These Arms Are Coming OffMarco ArmentFri, 06 Oct 2017 01:01:26 +0000http://atp.fm/episodes/242513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:59d5a0428a02c7e2d81439cf -
  • Pre-show: -
  • -
  • Follow up: John received his Apple TV 4K
  • -
  • #askatp -
  • -
  • New Amazon Echo announcements -
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show: John buys a chair -
  • - -

    Sponsored by:

    -
      -
    • Hullo Pillow: Your favorite pillow, guaranteed.
    • -
    • Betterment: Rethink what your money can do.
    • -
    • Casper: Dive deeper into the science behind the perfect mattress. Use code ATP for $50 toward any mattress purchase.
    • -
    ]]>
    ATPGoogle Pixels, Amazon Echoes, Sonos One, and expensive chairs.no01:59:15
    241: Jazzercise Boot CampMarco ArmentFri, 29 Sep 2017 21:15:35 +0000http://atp.fm/episodes/241513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:59cdbd1be3df283ef0b1ae36 -
  • Pre-show: Jazzercise 1982 video compilation
  • -
  • Follow up: -
  • -
  • #askatp
      -
    • Being good beta citizens and not making @tapbot_paul angry
    • -
    • APFS and Fusion drives
    • -
    -
  • -
  • Apple Watch Series 3 review -
  • -
  • 👋🏻⛰️ -
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show: Casey is a car journalist
  • - -

    Sponsored by:

    -
      -
    • Jamf Now: Set up and protect your Apple devices at work, from anywhere. First 3 devices are free.
    • -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    • Backblaze: Online backup for $5/month. Native. Unlimited. Unthrottled. Uncomplicated.
    • -
    ]]>
    ATPOur reviews of Apple Watch Series 3 and High Sierra, and a special Neutral post-show.no01:53:50
    240: Undefined Wait PeriodMarco ArmentThu, 21 Sep 2017 19:51:29 +0000http://atp.fm/episodes/240513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:59c3320146c3c40f64277c70 -
  • Stephen Hackett's St. Jude's Fundraiser
  • -
  • Follow up: -
  • -
  • More on Apple TV 4K
      -
    • Whether it has a fan or supports 24 Hz output
    • -
    • 32 vs. 64 GB
    • -
    -
  • -
  • Craig Federighi's Face ID apology tour -
  • -
  • Running Mac betas -
  • -
  • More on Apple Retail -
  • -
  • #askatp -
  • -
  • Training FaceID -
  • -
  • Last week's keynote, continued -
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show: -
  • - -

    Sponsored by:

    -
      -
    • Betterment: Rethink what your money can do. Get up to 1 year managed free.
    • -
    • Fracture: Photos printed in vivid color directly on glass. Get 10% off your first order.
    • -
    • AfterShokz: Headphones powered by bone-conduction technology.
    • -
    ]]>
    ATPA grab bag of updates and additional topics from the latest Apple releases.no02:16:53
    239: Set This Money on FireMarco ArmentThu, 14 Sep 2017 23:27:34 +0000http://atp.fm/episodes/239513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:59ba00cf7131a53af9540926 -
  • Apple Special Event, September 2017 -
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show: Marco figures out how to buy a bike -
  • - -

    Sponsored by:

    -
      -
    • Squarespace: Make your next move. Use code ATP for 10% off your first order.
    • -
    • Backblaze: Online backup for $5/month. Native. Unlimited. Unthrottled. Uncomplicated.
    • -
    • AfterShokz: Headphones powered by bone-conduction technology.
    • -
    ]]>
    ATPIn-depth analysis of the iPhone X event.no03:11:34
    238: Begging for the HubMarco ArmentThu, 07 Sep 2017 18:06:47 +0000http://atp.fm/episodes/238513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:59b0c0618419c2e19a1fbbe5 -
  • Follow-Up:
      -
    • Isn't iCloud close to full-machine backup?
    • -
    • Do you really need to spend $1000 on a camera?
    • -
    • Printing photos
    • -
    • Photo resolution
    • -
    -
  • -
  • #askatp -
  • -
  • John's 2017 MacBook Pro impressions -
  • -
  • iPhone 7 exit interview
  • -
  • iPhone 7S/8/Pro/Edition predictions
  • -
  • Apple Watch predictions
  • -
  • Wildcard predictions
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show: Marco is still obsessed with bikes
  • - -

    Sponsored by:

    -
      -
    • Betterment: Rethink what your money can do. Get up to 1 year managed free.
    • -
    • Hover: Find a domain name for your passion. Get 10% off your first purchase.
    • -
    • Away: Travel smarter with the suitcase that charges your phone. Get $20 off with code ATP.
    • -
    ]]>
    ATPPredictions for next week's event… after nearly two hours of other stuff.no02:33:30
    237: You Are Not a DatacenterMarco ArmentFri, 01 Sep 2017 16:58:20 +0000http://atp.fm/episodes/237513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:59a77e6ca9db09c622c8edab -
  • Follow-Up: -
  • -
  • #askatp -
  • -
  • APFS in High Sierra
  • -
  • No home button on "iPhone 8"?
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show: Caseys deep thought about batteries
  • - -

    Sponsored by:

    -
      -
    • Casper: Dive deeper into the science behind the perfect mattress. Use code ATP for $50 toward your mattress.
    • -
    • Audible: An unmatched selection of audiobooks, original audio shows, news, comedy, and more. Get a free 30-day trial.
    • -
    • Fracture: Photos printed in vivid color directly on glass. Get 10% off your first order.
    • -
    ]]>
    ATPMore on backups, camera advice, and whatever "sit bones" are.no01:54:50
    236: Irresponsible Use of Infinite TerabytesMarco ArmentThu, 24 Aug 2017 19:21:42 +0000http://atp.fm/episodes/236513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:599e4426e3df2831f0e7f1d8 -
  • Pre-show: -
  • -
  • Follow-Up: -
  • -
  • #askatp -
  • -
  • Casey's Bad News -
  • -
  • CrashPlan sunsets consumer backups -
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show: Marco's security adventures -
  • - -

    Sponsored by:

    -
      -
    • Hover: Find a domain name for your passion. Get 10% off your first purchase.
    • -
    • Betterment: Rethink what your money can do. Get up to 1 year managed free.
    • -
    • AfterShokz: Headphones powered by bone-conduction technology.
    • -
    ]]>
    ATPiTunes' future, huge media collections and NAS backups, and Marco buys some more stuff.no02:10:31
    235: Notch-SavvyMarco ArmentFri, 18 Aug 2017 05:28:40 +0000http://atp.fm/episodes/235513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:599508e349fc2b77f9de7cb9 -
  • Pre-show: Always on vacation in... New York?
  • -
  • Follow-Up: -
  • -
  • "iPhone Pro" notch
      -
    • What happens in landscape?
    • -
    • Why bother with a notch? -
    • -
    -
  • -
  • 1Password backpedals a bit
  • -
  • A a a a a Very Good Song
  • -
  • #askatp -
  • -
  • Google's diversity "memo" -
  • -
  • Apple sets $1B budget for original TV/movies
  • -
  • No more send-to-watch in Overcast -
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show: Marco's continued bike escapades -
  • - -

    Sponsored by:

    -
      -
    • Fracture: Photos printed in vivid color directly on glass. Get 10% off your first order.
    • -
    • AfterShokz: Headphones powered by bone-conduction technology.
    • -
    • Eero: Finally, Wi-Fi that works.
    • -
    ]]>
    ATPA a a a a Very Good Episode.no02:08:53
    234: Everybody Has AsterisksMarco ArmentFri, 11 Aug 2017 04:11:04 +0000http://atp.fm/episodes/234513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:598548f6ff7c50408a48aa64 -
  • Toasters -
  • -
  • Cameras -
  • -
  • Ad Blockers -
  • -
  • A/V -
  • -
  • Brace styles
  • -
  • Dolphin emulator behind the scenes
  • -
  • Ending theme by Jonathan Mann
  • - -

    Sponsored by:

    -
      -
    • Betterment: Investing made better.
    • -
    • Warby Parker: Boutique-quality, vintage-inspired glasses at a revolutionary price. Try up to five pairs at home for free.
    • -
    • Squarespace: Make your next move with a beautiful website. Use code ATP for 10% off your first order.
    • -
    ]]>
    ATPThe #askatp Q&A episode!no01:54:15
    233: Nobody Cares But Me, But I Do CareMarco ArmentSun, 06 Aug 2017 04:04:19 +0000http://atp.fm/episodes/233513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:59853178a5790a7d30c30555 -
  • Follow-Up: -
  • -
  • The HomePod firmware leak -
  • -
  • RIP [most] iPods
  • -
  • RIP Flash
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show: Storing photos -
  • - -

    Sponsored by:

    -
      -
    • Casper: The Casper mattress was designed by a team of 20 engineers, and perfected by a community of nearly half a million sleepers. Use code ATP for $50 toward your mattress.
    • -
    • Squarespace: Make your next move with a beautiful website. Use code ATP for 10% off your first order.
    • -
    • Audible: With Audible, you’ll find what you’re looking for. Get a free 30-day trial.
    • -
    ]]>
    ATPHomePod firmware, iPod Shuffles, bikes, and bug spray.no01:47:56
    232: You've Exceeded Some LimitMarco ArmentTue, 25 Jul 2017 23:30:00 +0000http://atp.fm/episodes/232513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:59717167440243149e88172a -
  • Send us questions! Tweet with the hashtag #askatp!
  • -
  • The A10X is 10nm as per AnandTech -
  • -
  • Netflix credits skipping -
  • -
  • Community management -
  • -
  • Odd Overcast crash
  • -
  • Post-show: Mongoose Californian -
  • - -

    Sponsored by:

    -
      -
    • Betterment: Investing made better.
    • -
    • Eero: Finally, Wi-Fi, that works. Use code ATP for free overnight shipping.
    • -
    • Fracture: Photos printed in vivid color directly on glass. Get 10% off your first order.
    • -
    ]]>
    ATPCleaning out the show-notes cabinet.no02:12:26
    231: I Am Not a Salad Power UserMarco ArmentThu, 20 Jul 2017 13:26:11 +0000http://atp.fm/episodes/231513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:596d79ee3a04110b1bb4e893 -
  • Follow-up: -
  • -
  • Send us questions! Tweet with the hashtag #askatp!
  • -
  • 1Password kerfuffle -
  • -
  • Overcast UI Dilemma -
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show: Neutral: Casey's latest impulse buy -
  • - -

    Sponsored by:

    -
      -
    • AfterShokz: Headphones powered by bone-conduction technology.
    • -
    • Fracture: Photos printed in vivid color directly on glass. Get 10% off your first order.
    • -
    • Hover: The best way to buy and manage domain names. Get 10% off your first purchase.
    • -
    ]]>
    ATPTV calibration, 1Password subscription rumblings, Overcast UI debates, and Casey's latest impulse buy.no01:53:15
    230: Auto, Dynamic, Fresh, DankMarco ArmentFri, 14 Jul 2017 00:41:25 +0000http://atp.fm/episodes/230513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5966e0a9d2b857c21290de7a -
  • Follow-up: -
  • -
  • Marco's latest impulse buy -
  • -
  • Ending theme by Jonathan Mann (a brief return of the Summer 2014 remix)
  • -
  • Post-show: -
  • - -

    Sponsored by:

    -
      -
    • Betterment: Investing made better.
    • -
    • Squarespace: Build it beautiful. Use code ATP for 10% off your first order.
    • -
    • Audible: With Audible, you’ll find what you’re looking for. Get a free 30-day trial.
    • -
    ]]>
    ATPMarco bought something again.no01:39:07
    229: Just Smush the Screen SomewhereMarco ArmentThu, 06 Jul 2017 16:14:37 +0000http://atp.fm/episodes/229513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:595da43044024313332c996d -
  • Follow-up: -
  • -
  • iOS 11 Multitasking -
  • -
  • New iPhone Rumors -
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show: LG UltraFine 5K Display
  • - -

    Sponsored by:

    -
      -
    • Casper: An obsessively engineered mattress at a shockingly fair price. Use code ATP for $50 toward your mattress.
    • -
    • Squarespace: Make your next move with a beautiful website. Use code ATP for 10% off your first order.
    • -
    • Fracture: Photos printed in vivid color directly on glass. Get 10% of your first order.
    • -
    ]]>
    ATPFace-unlock possibilities, iOS 11 multitasking so far, and the latest news about floppy drives.no01:36:05
    228: I Never Cancel a DragMarco ArmentThu, 29 Jun 2017 18:20:46 +0000http://atp.fm/episodes/228513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:59546f0372af659452cd5f97 -
  • Follow-up: -
  • -
  • Echo Show
  • -
  • iOS Drag and Drop -
  • -
  • dyld 3 -
  • -
  • Marco gets his VBR wish -
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show: -
  • - -

    Sponsored by:

    -
      -
    • Betterment: Investing made better.
    • -
    • Fracture: Photos printed in vivid color directly on glass. Get 10% of your first order.
    • -
    • Harry's: A great shave at a fair price. Get your free trial set.
    • -
    ]]>
    ATPEcho Show, USB-C hubs, iOS drag-and-drop, and dyld 3!no01:50:22
    227: Typing on PillowsMarco ArmentThu, 22 Jun 2017 03:55:54 +0000http://atp.fm/episodes/227513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5948976259cc684e3fe2522a -
  • Follow-up: -
  • -
  • Casey got a MacBook Adorable -
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show: -
  • - -

    Sponsored by:

    -
      -
    • Hover: Transfer your domain to Hover by the end of June to save 40% on your first year.
    • -
    • Squarespace: Build it beautiful. Use code ATP for 10% off your first order.
    • -
    • Away: Travel smarter with the suitcase that charges your phone. Get $20 off with code ATP.
    • -
    ]]>
    ATPAfter a Follow-Upocalypse, we finally hear about Casey's new family member.no01:58:21
    226: Smooth Scrolling Is For SuckersMarco ArmentThu, 15 Jun 2017 21:40:49 +0000http://atp.fm/episodes/226513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5941fe33ff7c503b3f7b148a -
  • Pre-show: Lots of Follow Up
  • -
  • John's ancient, slow, Mac Pro
  • -
  • APFS WWDC session and file name encoding -
  • -
  • iOS 11 -
  • -
  • Tidbits from The Talk Show #193: Crack Marketing Team
  • -
  • tvOS
  • -
  • iCloud storage: 1TB → 2TB
  • -
  • Sonnet eGFX
  • -
  • Apple Wireless Keyboard A1016
  • -
  • New iPads -
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show: -
  • - -

    Sponsored by:

    -
      -
    • Betterment: Investing made better.
    • -
    • Indochino: Finely crafted, exceptionally priced menswear. Get any custom-made suit for just $399 with code ATP.
    • -
    • MailRoute: Hosted spam and virus protection for email. Use this link for 10% off for the life of your account.
    • -
    ]]>
    ATPMuch more WWDC discussion, including what you've all been waiting for.no02:16:14
    225: A Conversation with SiriMarco ArmentTue, 06 Jun 2017 07:45:41 +0000http://atp.fm/episodes/225513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:59365b4815cf7df0e275d734Our WWDC 2017 live show!

    -

    Sponsored by:

    -
      -
    • Squarespace: Build it beautiful. Use code ATP for 10% off your first order.
    • -
    • Audible.com: With Audible, you’ll find what you’re looking for. Get a free 30-day trial.
    • -
    • Fracture: Photos printed in vivid color directly on glass. Get 10% of your first order.
    • -
    ]]>
    ATPOur WWDC 2017 live show!no01:47:53
    224: Yearning for ReflectionMarco ArmentThu, 01 Jun 2017 19:08:02 +0000http://atp.fm/episodes/224513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:592f8ebbd2b857caa394a720 -
  • Follow up: -
  • -
  • Pre-WWDC hopes and dreams -
  • -
  • Ending theme by Jonathan Mann
  • - -

    Sponsored by:

    -
      -
    • Jet: A shopping site that makes it easy to save money on the stuff you buy all the time. Use code ATP at checkout for 20% off your first 2 orders over $35 (terms and conditions apply).
    • -
    • Casper: An obsessively engineered mattress at a shockingly fair price. Use code ATP for $50 toward your mattress.
    • -
    • Betterment: Investing made better.
    • -
    ]]>
    ATPOur WWDC 2017 predictions and wishes.no02:40:49
    223: Throw the Fork AwayMarco ArmentThu, 25 May 2017 17:53:31 +0000http://atp.fm/episodes/223513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:592646963a04114633e6f227 -
  • Follow up: -
  • -
  • Denise Young Smith: VP, HR → VP, Inclusion & Diversity -
  • -
  • Thunderbolt to become royalty-free (Intel press release)
  • -
  • Apple Siri-speaker-with-screen
  • -
  • Google I/O -
  • -
  • Kotlin -
  • -
  • Ending theme by Jonathan Mann
  • - -

    Sponsored by:

    -
      -
    • Squarespace: Make your next move with a beautiful website. Use code ATP for 10% off your first order.
    • -
    • MailRoute: Hosted spam and virus protection for email. Use this link for 10% off for the life of your account.
    • -
    • Fracture: Photos printed in vivid color directly on glass. Get 10% of your first order.
    • -
    ]]>
    ATPWe tried really hard to talk about Google I/O.no01:43:09
    222: As Thin as Humans Will TolerateMarco ArmentThu, 18 May 2017 18:06:44 +0000http://atp.fm/episodes/222513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:591d0e9117bffc24f111b237 -
  • Follow up: Revisiting the quiz from last episode
  • -
  • MP3 is dead; long live MP3 -
  • -
  • iPad Mini is dead?
  • -
  • MacBooks and MacBook Pros are alive?
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show: -
  • - -

    Sponsored by:

    -
      -
    • Fracture: Photos printed in vivid color directly on glass.
    • -
    • Audible: With Audible, you’ll find what you’re looking for. Get a free 30-day trial.
    • -
    • Betterment: Investing made better.
    • -
    ]]>
    ATPThe reports of MP3's death are greatly exaggerated.no01:44:44
    221: Personal Body ChemistryMarco ArmentThu, 11 May 2017 17:27:16 +0000http://atp.fm/episodes/221513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:5913dd6ae3df28c1113dcf98 -
  • Follow up: -
  • -
  • Amazon Echo Show -
  • -
  • Brief Quiz
  • -
  • Dropping Dropbox
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show: Marco and Casey can't shut up about the Switch and Mario Kart
  • - -

    Sponsored by:

    -
      -
    • Warby Parker: Boutique-quality, vintage-inspired glasses at a revolutionary price. Try up to five pairs at home for free.
    • -
    • Squarespace: Build it beautiful. Use code ATP for 10% off your first order.
    • -
    • Away: Travel smarter with the suitcase that charges your phone. Get $20 off with code ATP.
    • -
    ]]>
    ATPAmazon Echo Show, dropping Dropbox, Mario Kart, and a "brief quiz".no02:10:48
    220: Executive Box LunchMarco ArmentThu, 04 May 2017 18:51:03 +0000http://atp.fm/episodes/220513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:590a9e9446c3c4b3041ee609 -
  • Follow up: Moscone food research by Daniel Jalkut
  • -
  • More on the potential rear Touch ID -
  • -
  • WikiTribune
  • -
  • Basic humanity -
  • -
  • Amazon Echo Look -
  • -
  • Microsoft Surface Laptop
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show: Casey really loves his new copy of Mario Kart 8 Deluxe -
  • - -

    Sponsored by:

    -
      -
    • Casper: An obsessively engineered mattress at a shockingly fair price. Use code ATP for $50 toward your mattress.
    • -
    • Indochino: Finely crafted, exceptionally priced menswear. Get any custom-made suit for just $399 with code ATP.
    • -
    ]]>
    ATPSurface Laptop, Echo Look, WikiTribune, and basic humanity.no02:09:57
    219: Million Dollar LunchMarco ArmentFri, 28 Apr 2017 00:01:32 +0000http://atp.fm/episodes/219513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:590161dfdb29d6372510b6b9 -
  • ATP WWDC Live at AltConf! Monday, 5 June at 5 PM PDT. Get tickets here, and hurry in case they sell out!
  • -
  • Follow up: -
  • -
  • Touch ID moving to the back? -
  • -
  • Overcast improvements -
  • -
  • Uber is über-gross -
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show: John travels to 🇬🇧 -
  • - -

    Sponsored by:

    -
      -
    • Squarespace: Make your next move with a beautiful website. Use code ATP for 10% off your first order.
    • -
    • Eero: Finally, Wi-Fi, that works.
    • -
    • Fracture: Photos printed in vivid color directly on glass.
    • -
    ]]>
    ATPiPhone rumors, Overcast Watch updates, yet more Uber shenanigans, and John's first international travel.no02:01:46
    218: MagSafe Cups of WaterMarco ArmentWed, 19 Apr 2017 13:42:34 +0000http://atp.fm/episodes/218513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:58f038afebbd1aeb69df9a70 -
  • Follow up: -
  • -
  • Apple USB-C MagSafe patent -
  • -
  • Beginning of the end for iTunes?
  • -
  • John's evaluation of Nintendo today -
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show: WWDC lunches -
  • - -

    Sponsored by:

    -
      -
    • Betterment: Investing made better.
    • -
    • Jamf Now: Set up and protect your Apple devices at work, from anywhere. First 3 devices are free.
    • -
    • MailRoute: Hosted spam and virus protection for email. Use this link for 10% off for the life of your account.
    • -
    ]]>
    ATPiTunes’ future, USB-C MagSafe, grading Nintendo, and a very special post-show.no01:39:31
    217: Apology Mac ProMarco ArmentWed, 12 Apr 2017 09:15:22 +0000http://atp.fm/episodes/217513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:58e701f36b8f5b4c80f76432 -
  • Follow up: -
  • -
  • Clips
  • -
  • Mac Pro -
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show:
      -
    • Marco convinces himself Casey owes him $5
    • -
    • Managing Mac Pro expectations
    • -
    • Apple speed bumps
    • -
    • MacRumors Buyer's Guide
    • -
    -
  • - -

    Sponsored by:

    -
      -
    • Squarespace: Build it beautiful. Use code ATP for 10% off your first order.
    • -
    • Audible: With Audible, you’ll find what you’re looking for. Get a free 30-day trial.
    • -
    • Fracture: Photos printed in vivid color directly on glass.
    • -
    ]]>
    ATPClips, Workflow, GPUs, a big Escape, and more Mac Pro!no01:58:17
    216: Thermal CornerMarco ArmentWed, 05 Apr 2017 18:49:01 +0000http://atp.fm/episodes/216513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:58e469565016e194dd58b6a7 -
  • Follow up: -
  • -
  • RIP Jason Seifer
  • -
  • The Mac Pro -
  • -
  • The new iMac... Pro? -
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show: -
  • - -

    Sponsored by:

    -
      -
    • Casper: An obsessively engineered mattress at a shockingly fair price. Use code ATP for $50 toward your mattress.
    • -
    • Betterment: Investing made better.
    • -
    • Indochino: Finely crafted, exceptionally priced menswear. Get any custom-made suit for just $399 with code ATP.
    • -
    ]]>
    ATPMac Pro news! (Really!)no02:23:45
    215: Pots Are Made to Be BrokenMarco ArmentThu, 30 Mar 2017 19:41:53 +0000http://atp.fm/episodes/215513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:58dc7251bf629a2d3b8f58db -
  • Follow up:
      -
    • APFS on iOS
        -
      • SettingsGeneralStorage & iCloud UsageManage Storage
      • -
      • Oddities with file names
      • -
      -
    • -
    • Ahoy, Telephone!
    • -
    • Naked-er Robotic Core
    • -
    -
  • -
  • Marco eats waffles -
  • -
  • MacBook One vs. MacBook Adorable -
  • -
  • WWDC -
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show: Tiff joins us to talk Zelda: Breath of the Wild -
  • - -

    Sponsored by:

    -
      -
    • Betterment: Investing made better.
    • -
    • Audible.com: With Audible, you’ll find what you’re looking for. Get a free 30-day trial.
    • -
    • Backblaze: Online backup for $5/month. Native. Unlimited. Unthrottled. Uncomplicated.
    • -
    ]]>
    ATPWho knew waffles were so delicious?no01:43:51
    214: Christmas PhoneMarco ArmentThu, 23 Mar 2017 21:13:21 +0000http://atp.fm/episodes/214513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:58d33b8ce4fcb51bf3a69393 -
  • Follow-up:
      -
    • Wii U display lag from Steven Impson
    • -
    • Zelda heads-up-display woes
    • -
    • Intel X99 high-end desktop options
    • -
    • Reasons for delaying the Mac Pro
    • -
    • Americans are cheap jerks
    • -
    -
  • -
  • Marco needs a new pair of shoelaces -
  • -
  • Apple executive diversity shareholder vote
  • -
  • Apple buys Workflow -
  • -
  • New Apple products -
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show: APFS news
  • - -

    Sponsored by:

    -
      -
    • Fracture: Photos printed in vivid color directly on glass. Use promo code CLEAN for 20% off by March 31.
    • -
    • HelloFresh: Delicious ingredients you'll love to eat. Simple recipes you'll live to cook. Use code ATP for $35 off your first shipment.
    • -
    • Squarespace: Build it beautiful. Use code ATP for 10% off your first order.
    • -
    ]]>
    ATPRed iPhones, nameless iPads, incredible waffles, and disruption to our workflows.no02:00:52
    213: Siri in a CanMarco ArmentThu, 16 Mar 2017 21:18:16 +0000http://atp.fm/episodes/213513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:58ca047aa5790a21846ec412 -
  • Pre-show: A friendly bet for January 1, 2018
  • -
  • Follow up: -
  • -
  • Apple's HomeKit Promo Website & Video -
  • -
  • YouTube TV (Verge Writeup)
  • -
  • Intel goes 14nm again
  • -
  • AMD Ryzen
  • -
  • Xeon Gold
  • -
  • Tim Cook's tease at the shareholders meeting
  • -
  • Ending theme by Jonathan Mann
  • -
  • Post-show: Initital thoughts on the Nintendo Switch
  • - -

    Sponsored by:

    -
      -
    • Eero: Finally, Wi-Fi, that works.
    • -
    • Betterment: Investing made better.
    • -
    • Squarespace: Build it beautiful. Use code ATP for 10% off your first order.
    • -
    ]]>
    ATPYouTube TV, Ryzen, HomeKit, and the cylinder business.no02:13:52
    212: Meatspace WindowsMarco ArmentThu, 09 Mar 2017 06:09:41 +0000http://atp.fm/episodes/212513abd71e4b0fe58c655c105:513abd71e4b0fe58c655c111:58c0d681a5790ae223f39d52 -
  • Follow-up: USB-C iPhone 8 -
  • -
  • MacBook Boost case
  • -
  • Windowing on iOS -
  • -
  • Accidental programmers -
  • -
  • Apple's decline in the education market
  • -
  • Ending theme by Jonathan Mann
  • - -

    Sponsored by:

    -
      -
    • Away: Travel smarter with the suitcase that charges your phone. Get $20 off with code ATP.
    • -
    • Squarespace: Build it beautiful. Use code ATP for 10% off your first order.
    • -
    • Pingdom: Uptime and performance monitoring made easy. Use code ATP for 20% off.
    • -
    ]]>
    ATPJohn still really, really likes windows.no01:55:34
    \ No newline at end of file diff --git a/RSParser/Tests/RSParserTests/Resources/authors.json b/RSParser/Tests/RSParserTests/Resources/authors.json deleted file mode 100644 index 2d0b65b17..000000000 --- a/RSParser/Tests/RSParserTests/Resources/authors.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "version": "https://jsonfeed.org/version/1.1", - "title": "Author test feed", - "authors": [{ "name": "Root Author 1"}, { "name": "Root Author 2" }], - "author": { "name": "Legacy Root Author" }, - "items": [ - { - "id": "Item without authors", - "content_html": "" - }, - { - "id": "Item with legacy author", - "author": { "name": "Legacy Item Author" }, - "content_html": "" - }, - { - "id": "Item with modern authors", - "authors": [{ "name": "Item Author 1" }, { "name": "Item Author 2" }], - "content_html": "" - }, - { - "id": "Item with both", - "authors": [{ "name": "Item Author 1" }, { "name": "Item Author 2" }], - "author": { "name": "Legacy Item Author" }, - "content_html": "" - } - ] -} diff --git a/RSParser/Tests/RSParserTests/Resources/bio.rdf b/RSParser/Tests/RSParserTests/Resources/bio.rdf deleted file mode 100644 index 215ada165..000000000 --- a/RSParser/Tests/RSParserTests/Resources/bio.rdf +++ /dev/null @@ -1,663 +0,0 @@ - - - -bioRxiv Subject Collection: Plant Biology -http://biorxiv.org - -This feed contains articles for bioRxiv Subject Collection "Plant Biology" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bioRxiv - - - - - -bioRxiv - -http://biorxiv.org - - - -<![CDATA[ -Wheat inositol pyrophosphate kinase (TaVIH2-3B) interacts with Fasciclin-like arabinogalactan (FLA6) protein and alters the plant cell-wall composition -]]> - - -http://biorxiv.org/cgi/content/short/743294v1?rss=1 - - - -2019-08-27 -doi:10.1101/743294 - -Cold Spring Harbor Laboratory -2019-08-27 - - - - -<![CDATA[ -Natural variation in the Arabidopsis AGO2 gene is associated with susceptibility to potato virus X -]]> - - -http://biorxiv.org/cgi/content/short/746628v1?rss=1 - - - -2019-08-24 -doi:10.1101/746628 - -Cold Spring Harbor Laboratory -2019-08-24 - - - - -<![CDATA[ -Dynamic architecture and regulatory implications of the miRNA network underlying the response to stress in melon -]]> - - -http://biorxiv.org/cgi/content/short/745653v1?rss=1 - - - -2019-08-24 -doi:10.1101/745653 - -Cold Spring Harbor Laboratory -2019-08-24 - - - - -<![CDATA[ -Algal photosynthesis converts nitric oxide into nitrous oxide -]]> - - -http://biorxiv.org/cgi/content/short/745463v1?rss=1 - - - -2019-08-24 -doi:10.1101/745463 - -Cold Spring Harbor Laboratory -2019-08-24 - - - - -<![CDATA[ -Cytokinin fluoroprobe and receptor CRE1/AHK4 localize to both plasma membrane and endoplasmic reticulum -]]> - - -http://biorxiv.org/cgi/content/short/744177v1?rss=1 - - - -2019-08-24 -doi:10.1101/744177 - -Cold Spring Harbor Laboratory -2019-08-24 - - - - -<![CDATA[ -Transcriptional Dynamics of the Salicylic Acid Response and its Interplay with the Jasmonic Acid Pathway -]]> - - -http://biorxiv.org/cgi/content/short/742742v1?rss=1 - - - -2019-08-24 -doi:10.1101/742742 - -Cold Spring Harbor Laboratory -2019-08-24 - - - - -<![CDATA[ -Dynamic response of RNA editing to temperature in Grape by RNA deep-sequencing -]]> - - -http://biorxiv.org/cgi/content/short/745364v1?rss=1 - - - -2019-08-24 -doi:10.1101/745364 - -Cold Spring Harbor Laboratory -2019-08-24 - - - - -<![CDATA[ -Genome-wide analysis of GATA factors in moso bamboo (Phyllostachys edulis) unveils that PeGATAs regulate shoot rapid-growth and rhizome development -]]> - - -http://biorxiv.org/cgi/content/short/744003v1?rss=1 - - - -2019-08-22 -doi:10.1101/744003 - -Cold Spring Harbor Laboratory -2019-08-22 - - - - -<![CDATA[ -The maize Hairy Sheath Frayed1 (Hsf1) mutant alters leaf patterning through increased cytokinin signaling -]]> - - -http://biorxiv.org/cgi/content/short/743898v1?rss=1 - - - -2019-08-22 -doi:10.1101/743898 - -Cold Spring Harbor Laboratory -2019-08-22 - - - - -<![CDATA[ -Expression atlas of Selaginella moellendorffii provides insights into the evolution of vasculature, secondary metabolism and roots -]]> - - -http://biorxiv.org/cgi/content/short/744326v1?rss=1 - - - -2019-08-22 -doi:10.1101/744326 - -Cold Spring Harbor Laboratory -2019-08-22 - - - - -<![CDATA[ -Soybean drought resilience: contributions of a brassinosteroid functional analogue. -]]> - - -http://biorxiv.org/cgi/content/short/742429v1?rss=1 - - - -2019-08-22 -doi:10.1101/742429 - -Cold Spring Harbor Laboratory -2019-08-22 - - - - -<![CDATA[ -Co-catabolism of arginine and succinate drives symbiotic nitrogen fixation -]]> - - -http://biorxiv.org/cgi/content/short/741314v1?rss=1 - - - -2019-08-21 -doi:10.1101/741314 - -Cold Spring Harbor Laboratory -2019-08-21 - - - - -<![CDATA[ -Cross-compatibility of five highbush blueberry varieties and ideal crossing combinations -]]> - - -http://biorxiv.org/cgi/content/short/742114v1?rss=1 - - - -2019-08-21 -doi:10.1101/742114 - -Cold Spring Harbor Laboratory -2019-08-21 - - - - -<![CDATA[ -Symbiotic signalling is at the core of an endophytic Fusarium solani-legume association -]]> - - -http://biorxiv.org/cgi/content/short/740043v1?rss=1 - - - -2019-08-20 -doi:10.1101/740043 - -Cold Spring Harbor Laboratory -2019-08-20 - - - - -<![CDATA[ -NbCycB2 represses Nbwo activity via a negative feedback loop in the tobacco trichome developmemt -]]> - - -http://biorxiv.org/cgi/content/short/740126v1?rss=1 - - - -2019-08-20 -doi:10.1101/740126 - -Cold Spring Harbor Laboratory -2019-08-20 - - - - -<![CDATA[ -Successes of artemisinin elicitation in low-artemisinin producing Artemisia annua cell cultures constrained by repression of biosynthetic genes -]]> - - -http://biorxiv.org/cgi/content/short/740167v1?rss=1 - - - -2019-08-19 -doi:10.1101/740167 - -Cold Spring Harbor Laboratory -2019-08-19 - - - - -<![CDATA[ -A SAC phosphoinositide phosphatase controls rice development via hydrolyzing phosphatidylinositol 4-phosphate and phosphatidylinositol 4,5-bisphosphate -]]> - - -http://biorxiv.org/cgi/content/short/740001v1?rss=1 - - - -2019-08-19 -doi:10.1101/740001 - -Cold Spring Harbor Laboratory -2019-08-19 - - - - -<![CDATA[ -Integrated Multi-omic Framework of the Plant Response to Jasmonic Acid -]]> - - -http://biorxiv.org/cgi/content/short/736363v1?rss=1 - - - -2019-08-19 -doi:10.1101/736363 - -Cold Spring Harbor Laboratory -2019-08-19 - - - - -<![CDATA[ -Developing a rapid and highly efficient cowpea regeneration and transformation system using embryonic axis explants -]]> - - -http://biorxiv.org/cgi/content/short/738971v1?rss=1 - - - -2019-08-19 -doi:10.1101/738971 - -Cold Spring Harbor Laboratory -2019-08-19 - - - - -<![CDATA[ -Genetic analysis of seed and pod traits in a set of Recombinant Inbred Lines (RILs) in peanut (Arachis hypogaea L.) -]]> - - -http://biorxiv.org/cgi/content/short/738914v1?rss=1 - -20% variance) were attributed to NC 3033 and located in a single linkage group, LG B06_1. In contrast, the most consistent QTL for kernel percentage were located on A07/B07 and derived from Tifrunner. -]]> - -2019-08-18 -doi:10.1101/738914 - -Cold Spring Harbor Laboratory -2019-08-18 - - - - -<![CDATA[ -Characterizing allele-by-environment interactions using maize introgression lines -]]> - - -http://biorxiv.org/cgi/content/short/738070v1?rss=1 - -90% B73 genetic background and multiple Mo17 introgressions were grown in 16 different environments. These environments included five geographical locations with multiple planting dates and multiple planting densities. The phenotypic impact of the introgressions was evaluated for up to 26 traits that span different growth stages in each environment to assess allele-by-environment interactions. Results from this study showed that small portions of the genome can drive significant genotype-by-environment interaction across a wide range of vegetative and reproductive traits, and the magnitude of the allele-by-environment interaction varies across traits. Some introgressed segments were more prone to genotype-by-environment interaction than others when evaluating the interaction on a whole plant basis throughout developmental time, indicating variation in phenotypic plasticity throughout the genome. Understanding the profile of allele-by-environment interaction is useful in considerations of how small introgressions of QTL or transgene containing regions might be expected to impact traits in diverse environments.nnKey MessageSignificant allele-by-environment interactions are observed for traits throughout development from small introgressed segments of the genome. -]]> - -2019-08-16 -doi:10.1101/738070 - -Cold Spring Harbor Laboratory -2019-08-16 - - - - -<![CDATA[ -A modular cloning toolkit for genome editing in plants -]]> - - -http://biorxiv.org/cgi/content/short/738021v1?rss=1 - - - -2019-08-16 -doi:10.1101/738021 - -Cold Spring Harbor Laboratory -2019-08-16 - - - - -<![CDATA[ -Evaluation of 20 enset (Ensete ventricosum) landraces for response to Xanthomonas vasicola pv. musacearum infection -]]> - - -http://biorxiv.org/cgi/content/short/736793v1?rss=1 - - - -2019-08-15 -doi:10.1101/736793 - -Cold Spring Harbor Laboratory -2019-08-15 - - - - -<![CDATA[ -A peptide pair coordinates regular ovule initiation patterns with seed number and fruit size -]]> - - -http://biorxiv.org/cgi/content/short/736439v1?rss=1 - - - -2019-08-15 -doi:10.1101/736439 - -Cold Spring Harbor Laboratory -2019-08-15 - - - - -<![CDATA[ -Multi-Dimensional Machine Learning Approaches for Fruit Shape Recognition and Phenotyping in Strawberry -]]> - - -http://biorxiv.org/cgi/content/short/736397v1?rss=1 - - - -2019-08-15 -doi:10.1101/736397 - -Cold Spring Harbor Laboratory -2019-08-15 - - - - -<![CDATA[ -An ethnobotanical study of the genus Elymus -]]> - - -http://biorxiv.org/cgi/content/short/734525v1?rss=1 - - - -2019-08-15 -doi:10.1101/734525 - -Cold Spring Harbor Laboratory -2019-08-15 - - - - -<![CDATA[ -Drought sensitivity of leaflet growth, biomass accumulation, and resource partitioning predicts yield in common bean -]]> - - -http://biorxiv.org/cgi/content/short/736199v1?rss=1 - - - -2019-08-15 -doi:10.1101/736199 - -Cold Spring Harbor Laboratory -2019-08-15 - - - - -<![CDATA[ -Dynamic regulation of immunity through post-translational control of defense transcript splicing -]]> - - -http://biorxiv.org/cgi/content/short/736249v1?rss=1 - - - -2019-08-15 -doi:10.1101/736249 - -Cold Spring Harbor Laboratory -2019-08-15 - - - - -<![CDATA[ -How do three cytosolic glutamine synthetase isozymes of wheat perform N assimilation and translocation? -]]> - - -http://biorxiv.org/cgi/content/short/733857v1?rss=1 - - - -2019-08-14 -doi:10.1101/733857 - -Cold Spring Harbor Laboratory -2019-08-14 - - - - -<![CDATA[ -Cell-surface receptors enable perception of extracellular cytokinins -]]> - - -http://biorxiv.org/cgi/content/short/726125v1?rss=1 - - - -2019-08-14 -doi:10.1101/726125 - -Cold Spring Harbor Laboratory -2019-08-14 - - - - - - - diff --git a/RSParser/Tests/RSParserTests/Resources/cloudblog.rss b/RSParser/Tests/RSParserTests/Resources/cloudblog.rss deleted file mode 100644 index af022cbbd..000000000 --- a/RSParser/Tests/RSParserTests/Resources/cloudblog.rss +++ /dev/null @@ -1,2 +0,0 @@ - -Cloud Bloghttps://cloud.google.com/blog/Cloud BlogenTue, 03 Sep 2019 18:00:00 -0000https://gweb-cloudblog-publish.appspot.com/static/blog/images/google.a51985becaa6.pngCloud Bloghttps://cloud.google.com/blog/Want to keep your employees productive? Pay attention to shadow IT clueshttps://cloud.google.com/blog/products/productivity-collaboration/want-to-keep-your-employees-productive-pay-attention-to-shadow-it-clues/<html><head></head><body><div class="block-paragraph"><div class="rich-text"><p>Employees use tools at their disposal to get work done, but if these tools (often legacy) hamper collaboration or are inflexible, they’ll turn to less secure options for the sake of convenience. According to <a href="https://www.gartner.com/smarterwithgartner/top-10-security-predictions-2016/">Gartner</a>, a third of successful attacks experienced by enterprises will come from Shadow IT usage by 2020. </p><p>And this problem is not unknown. Eighty-three percent of IT professionals <a href="https://www.prnewswire.com/news-releases/shadow-it---cloud-usage-a-growing-challenge-for-cios-575359961.html">reported</a> that employees stored company data in unsanctioned cloud services, a challenge especially apparent with file sync and share tools. When people work around their legacy systems to use tools like Google Drive, it’s often because they find their current systems to be clunky or that they can’t collaborate with others as easily. They’re unable to do three key things in legacy file sync and share systems (like Microsoft SharePoint):</p><ol><li><b>Unable to work on their phones.</b> By now, people expect to be able to work on the go—and this means not just opening an attachment, but actually making edits to and comments on work. It gives them freedom to work when it’s convenient for them and to help teammates anytime. </li><li><b>Unable to create workspaces independently and easily.</b> This might sound counterintuitive, but if an employee needs to contact IT to have a new project folder made on a drive, the bar is too high. Employees need to be able to quickly, and independently, create documents that can be shared simply because of the changing nature of collaboration. Work happens ad-hoc, on the go (like we mentioned above), and with people inside and outside of your organization. If someone has to contact IT to create a new folder, they’re more likely to neglect the request or use a different tool altogether to get started. </li><li><b>Unable to make the data work for them.</b> Traditional file storage is just that, storage. Like an attic, we store things in these systems, but at some point stuff gets stale and it’s hard to tell what we should keep or pitch. People need their storage systems to not only house their data, but to help them categorize and find information quicker so that they can make this data work better for them.</li></ol><p>The way I see it, you have two choices when it comes to making a decision on file sync and share systems:</p><p><i>Option 1:</i> Continue to let your employees work on unsanctioned products, some of which may open your business up to unintended security issues (and, in some instances, scary terms of service).</p><p><i>Option 2:</i> Buy the tools that your users want to use because these tools are making them more productive.</p><p>If you want to create a more productive workforce, take cues from your employees. Your tools should not only meet the highest security standards for IT, but let people work the way they want to (and be intelligent enough to guide them along the way). Imagine if your technology could flag that a file contains confidential information before an employee accidentally shares it. Or surface files as they’re needed to help people work faster. <a href="https://inthecloud.withgoogle.com/drive/replace-sharepoint.html?utm_source=cloudblog&amp;utm_medium=drive&amp;utm_campaign=replacingsharepoint">Google Drive does this</a>.</p><p>Remember, if the technology doesn’t suit your employees, they’re just going to work around it anyway. Instead of investing time and resources on routine maintenance, shift this energy toward helping your employees stay productive in ways that work for both you and them.</p></div></div></body></html>Tue, 03 Sep 2019 18:00:00 -0000https://cloud.google.com/blog/products/productivity-collaboration/want-to-keep-your-employees-productive-pay-attention-to-shadow-it-clues/PerspectivesDriveInside Google CloudProductivity & CollaborationarticleWant to keep your employees productive? Pay attention to shadow IT clueshttps://storage.googleapis.com/gweb-cloudblog-publish/images/shadow_IT.max-600x600.pngGooglehttps://cloud.google.com/blog/products/productivity-collaboration/want-to-keep-your-employees-productive-pay-attention-to-shadow-it-clues/Diane ChaleffProduct Manager, Office of the CTOLast month today: August on GCPhttps://cloud.google.com/blog/products/gcp/last-month-today-august-2019-on-gcp/<html><head></head><body><div class="block-paragraph"><div class="rich-text"><p>Last month on the <a href="https://cloud.google.com/">Google Cloud Platform</a> (GCP) blog, we dove into hardware, software, and the humans who make technology work. Here’s what topped our charts in August.</p><p><b>Exploring the nuts and bolts of cloud</b></p><ul><li><p>Google already uses AMD’s EPYC processors for internal workloads, and last month we announced <a href="https://cloud.google.com/blog/products/compute/amd-epyc-processors-come-to-google-and-to-google-cloud">that they’re coming to the data centers that power Google Cloud</a> products. Second-gen AMD EPYC processors will soon power our new virtual machines—the largest general-purpose VMs we’ve ever offered. There will be a range of sizes for these AMD VMs so you can choose accordingly, and can also configure them as custom machine types. Improvements like these can help you get more performance for the price for your workloads. </p></li></ul><ul><li><p>One small button can make it easy for other developers to deploy your app to GCP using Cloud Run, our managed compute platform that lets you deploy containerized serverless apps. You can add the <a href="https://cloud.google.com/blog/products/serverless/introducing-cloud-run-button-click-to-deploy-your-git-repos-to-google-cloud">new Cloud Run Button</a> to any source code repository that has a dockerfile or that can be built with Cloud Native Buildpacks. One click will package the app source code as a container image, push it to Google Container Registry, then deploy it on Cloud Run. </p></li></ul><p><b>Looking at the human side of technology</b></p><ul><li><p>This <a href="https://cloud.google.com/blog/topics/hybrid-cloud/a-cios-guide-to-the-cloud-hybrid-and-human-solutions-to-avoid-trade-offs">blog post offered a look at the tradeoffs that CIOs and CTOs</a> have to make in their pursuit of business acceleration in a hybrid world, based on recent McKinsey research. While digital transformation and new tech capabilities are in high demand, leaders can avoid making tradeoffs by choosing technology wisely and making necessary operational changes too, including fostering a change mindset. There are tips here on embracing a DevOps model, using a flexible hybrid cloud model, and adopting open-source architectures to avoid common pitfalls.</p></li><li><p>This year’s <a href="https://cloud.google.com/blog/products/devops-sre/the-2019-accelerate-state-of-devops-elite-performance-productivity-and-scaling">Accelerate State of DevOps Report is available now</a>, and offers a look at the latest in DevOps, with tips for organizations at all stages of DevOps maturity. This year, data shows that the percentage of elite performers is at its highest ever, and that these elite performers are more likely to use cloud. The report found that most cloud users still aren’t getting all of its benefits, though. DevOps should be a team effort, too, with both organizational and team-level efforts important for success.</p></li></ul><p><b>How customers are developing with cloud</b></p><ul><li><p>Google Cloud customers are pushing innovation further to serve customers in lots of interesting ways. First up this month is <a href="https://cloud.google.com/blog/topics/customers/macys-uses-google-cloud-to-streamline-retail-operations">Macy’s, which uses Google Cloud</a> to help provide customers with great online and in-person experiences. The company is streamlining retail operations across its network with cloud, and uses GCP’s data warehousing and analytics to optimize all kinds of merchandise tasks at its new distribution center.</p></li><li><p>We also heard this month from <a href="https://cloud.google.com/blog/products/ai-machine-learning/itau-unibanco-how-we-built-a-cicd-pipeline-for-machine-learning-with-online-training-in-kubeflow">Itau Unibanco of Brazil, which developed a digital customer service tool</a> to offer instant help to bank users. They use Google Cloud to build a Kubeflow-based CI/CD pipeline to deploy machine learning models and serve customers quickly and accurately. The post offers a look at their architecture and offers tips for replicating the pipeline.</p></li></ul><ul><li><p>Last but not least, check out this story on <a href="https://cloud.google.com/blog/products/maps-platform/how-two-developers-reached-new-heights-with-google-maps-platform">how web developers are using Google Maps Platform and custom Street View imagery</a> to offer virtual tours to the top of Zugspitze, the tallest mountain in Germany. Along with exploring APIs and deciding how to use the technology, the developers took a ton of 360° photos while hiking up and down parts of the 10,000-foot mountain. Take the tour yourself <a href="https://zugspitze360.com/">on their site</a>.</p></li></ul><p>That’s a wrap for August! Stay tuned <a href="https://cloud.google.com/blog/">on the blog</a> for all the latest.</p></div></div></body></html>Tue, 03 Sep 2019 14:00:00 -0000https://cloud.google.com/blog/products/gcp/last-month-today-august-2019-on-gcp/Google Cloud PlatformarticleLast month today: August on GCPHere are some of the top GCP stories that appeared on the Cloud blog in August.https://storage.googleapis.com/gweb-cloudblog-publish/images/Last_Month_Today_Aug19.max-600x600.jpgGooglehttps://cloud.google.com/blog/products/gcp/last-month-today-august-2019-on-gcp/The Google Cloud blog team Build a dev workflow with Cloud Code on a Pixelbookhttps://cloud.google.com/blog/products/application-development/build-a-dev-workflow-with-cloud-code-on-a-pixelbook/<html><head></head><body><div class="block-paragraph"><div class="rich-text"><p>Can you use a Pixelbook for serious software development? Do you want a workflow that is simple, doesn’t slow you down, and is portable to other platforms? And do you need support for Google Cloud Platform SDK, Kubernetes and Docker? I switched to a Pixelbook for development, and I love it!</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="Cloud Code.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/Cloud_Code_tOGSx5R.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Pixelbooks are slim, light, ergonomic, and provide great performance. Chrome OS is simple to use. It brings many advantages over traditional operating systems: </p><ul><li>frictionless updates</li><li>enhanced security</li><li>extended battery life</li></ul><p>And the most compelling feature for me: almost <b>instant coming to life after sleep</b>. This is great when hopping between meetings and on the road. </p><p>A little about me - I’m a <a href="https://medium.com/google-cloud/developer-programs-engineer-say-what-b12829729693">Developer Programs Engineer</a>. I work on Google Cloud and contribute to many open source projects. I need to accomplish repeatable development tasks: working with Github, build, debug, deploy and observe. Running and testing the code on multiple platforms is also of high importance. I can assure you, the workflow below built on Pixelbook satisfies all the following:</p><ul><li>Simple, repeatable development workflow with emphasis on developer productivity</li><li>Portable to other platforms (Linux, MacOS, Windows)—“create once, use everywhere”</li><li>Support for Google Cloud Platform SDK, Github, Kubernetes and Docker.</li></ul><p>Let’s dive into how you can set up a development environment on Pixelbook that meets all those requirements using <a href="https://cloud.google.com/code/docs/vscode/quickstart">Cloud Code for Visual Studio Code</a>, remote extensions, and several other handy tools. If you are new to the world of Chromebooks and switching from a PC, check out <a href="https://cloud.google.com/blog/products/chrome-enterprise/how-to-use-a-chromebook-if-youve-switched-from-a-pc">this post</a> to get started.</p><h2>Step 1: Enable Linux apps on Pixelbook</h2><p>Linux for Chromebooks (aka <a href="https://chromium.googlesource.com/chromiumos/docs/+/master/containers_and_vms.md#Crostini">Crostini</a>) is a project to let developers do everything they need locally on a Chromebook, with an emphasis on web and Android app development. It adds Linux support.  </p><p>On your Pixelbook:</p>1. Go to Settings (chrome://settings) in the built-in Chrome browser.<br/>2. Scroll down to the “Linux (Beta) ” section (see screenshot below).<br/></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="Enable Linux apps on Pixelbook.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/Enable_Linux_apps_on_Pixelbook1.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text">3. Click “<b>Turn on</b>” and follow the prompts. It may take up to 10 minutes depending on your Wi-Fi connection.<br/>4. At the end, a new Terminal window should automatically open to a shell within the container. We’re all set to continue to the next step - installing developer tools!<p><br/></p><p>Pin the terminal window to your program bar for convenience.</p><p><b>Configure Pixelbook keyboard to respect Function keys<br/></b>Folks coming from Windows or MacOS backgrounds are used to using Function keys for development productivity. On Chrome OS, they are replaced by default to a group of shortcuts. </p><p>However, we can bring them back:</p><p>Navigate to chrome://settings. Now, pick “Device” on the left menu, then pick “keyboard”. Toggle “treat top-row keys as function keys”:</p><p></p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="Configure Pixelbook keyboard to respect Function key.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/pixelbook1.1000064220000303.max-1000x1000.jpg"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><h2>Step 2: Install development tools</h2><p>For Kubernetes development on GCP, we need to install tools like Docker, Google Cloud SDK and kubectl. Pixelbook Linux is Debian Stretch, so we will install prerequisites for docker and gcloud using instructions for Debian Stretch distribution.</p><p><b>Install and configure Google Cloud SDK (gcloud):<br/></b>Run these commands from <a href="https://cloud.google.com/sdk/docs/quickstart-debian-ubuntu">gcloud Debian quickstart</a> to install gcloud sdk:</p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><p><b>Troubleshooting<br/></b>You might run into this error:</p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Your keyrings are out of date. Run the following commands and try the Cloud SDK commands again:</p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><p><b>Add gcloud to PATH</b></p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><p><b>Installing Docker CE for Linux:<br/></b>Follow these <a href="https://docs.docker.com/install/linux/docker-ce/debian">instructions</a>.</p><p>And then add your user to the docker group:</p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><p><i><b>NOTE:</b> This allows running docker commands without sudo.</i></p><p><b>Install kubectl</b></p></div></div><div class="block-paragraph"><div class="rich-text"><p><b>Installing Visual Studio Code</b></p><p>Go to <a href="https://code.visualstudio.com/docs/setup/linux">VSCode linux install instructions page</a>.</p><ol><li><p>Download the<a href="https://go.microsoft.com/fwlink/?LinkID=760868">.deb package (64bit)</a> from the link on the page.</p></li><li><p>After the download is complete, install the deb file using “Install app with Linux (beta)”:</p></li></ol></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="Installing Visual Studio Code.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/Installing_Visual_Studio_Code.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p><b>Troubleshooting<br/></b>If you don’t see “Install with Linux” as an option for the deb file, double check that you switched to the beta channel.</p><p>Now let’s install a few extensions that I find helpful when working on a remote container using VS Code:</p><ul><li><p><a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-docker">Docker</a> - managing docker images, autocompletion for docker files, and more.</p></li></ul><ul><li><p><a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers">Remote Containers</a> - use a docker container as a full-featured development environment. </p></li></ul><p>These two, along with Cloud Code, are key extensions in our solution.</p><h2>Step 3: Configuring Github access</h2><p><b>Configure github with SSH key</b></p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Now copy and past the key into <a href="https://github.com/settings/keys">Github</a>.</p><p><i><b>NOTE:</b>If facing permissions error doing ssh-add, run <b>sudo chown $USER .ssh</b> and re-run all the steps for github setup again.</i></p><p>Set the username and email of github:</p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><h2>Step 4: Remote development</h2><p>Now that we have the tools installed and Github access configured, let’s configure our development workflow. In order to create a solution that is portable to other platforms, we will use remote containers extension. We will create a container that will be used to build, deploy and debug applications that we create. This is how it will work:</p></div></div><div class="block-paragraph"><div class="rich-text"><p>We will open our codebase in a remote container. This will let VS Code think that it is open in isolated Linux environment, so everything we do (build, deploy, debug, file operations) will be interpreted as if we were working on a dedicated Linux VM with its own file system: every command we execute on VS Code will be sent for execution on our remote container. This way we achieve the goal of portability—remote Linux container can run on both MacOS and Windows just like we do it on Pixelbook with Chrome OS that supports Linux.</p><h2>Dev Container settings for each repo</h2><p>Here’s how to set up a dev container for an existing project. You can find the full source code in the <a href="https://github.com/GoogleCloudPlatform/cloud-code-samples">Cloud Code templates repo</a>. This Github repo includes templates for getting started with repeatable Kubernetes development in five programming languages—Node.js, Go, Java, Python and .NET. Each template includes configuration for debugging and deploying the template to Kubernetes cluster using <a href="https://marketplace.visualstudio.com/items?itemName=GoogleCloudTools.cloudcode">Cloud Code for VS Code</a> and <a href="https://cloud.google.com/intellij/">IntelliJ</a>. For simplicity, we work with a HelloWorld template that just serves “Hello World” message from a simple web server in a single container.</p><p>To enable remote container development, we need to add a <b>.devcontainer</b> folder with two files:</p><ul><li><p><b>Dockerfile</b> — defines container image that holds all developer tools we need installed in a remote development container</p></li><li><p><b>Devcontainer.json</b> — Instructs VS Code Remote Tools extension how to run remote development container.</p></li></ul><p><b>Creating a container image for remote development<br/></b>Our remote container needs to have the SDK we use for development in the programming language of our choice. In addition, it needs tools that enable Cloud Code and Kubernetes workflows on Google Cloud. Therefore in the <a href="https://github.com/GoogleCloudPlatform/cloud-code-samples/blob/master/nodejs/nodejs-hello-world/.devcontainer/Dockerfile">Dockerfile</a> we install:</p><ul><li><p><a href="https://cloud.google.com/sdk/">Google Cloud SDK</a></p></li><li><p><a href="https://skaffold.dev">Skaffold</a> — tool Cloud Code uses for handling the workflow for building, pushing and deploying apps in containers</p></li><li><p><a href="https://docs.docker.com/engine/reference/commandline/cli/">Docker CLI</a></p></li></ul><p>In addition, container images are immutable. Every time we open the code in a remote container, we’ll get a clean state—no extra settings will be persisted between remote container reloads by default (kubernetes clusters to work with, gcloud project configuration, github ssh keys). To address that, we mount our host folders as drives in the container (see this part later in <a href="https://github.com/GoogleCloudPlatform/cloud-code-samples/blob/master/nodejs/nodejs-hello-world/.devcontainer/devcontainer.json">devcontainer.json</a>) and copy its content to the folder in the container file system where dev tools expect to find these files. </p><p>Example from Dockerfile of kubeconfig, gcloud and ssh keys sync between host and remote container:</p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><p><b>devcontainer.json<br/></b>This file tells Remote Container extension which ports to expose in the container, how to mount drives, which extensions to install in the remote container, and more.</p><p>A few notable configurations:</p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><p><i>runArgs</i> contains command line arguments remote extension passes to docker when remote container is launched. This is where we set environment variables and mount external drives in a container. This helps to eliminate authorizations and specifies the kubernetes clusters we want to work with in Cloud Code.</p><p>In the <i>extensions</i> section, we add a few VS Code extensions for enhanced productivity in the development container. These will be installed on a dev container but not on the host, so you can tailor this choice to the codebase you plan to work on in the dev container. In this case I am setting up for nodejs development.</p><ul><li><p><a href="https://marketplace.visualstudio.com/items?itemName=GoogleCloudTools.cloudcode"><b>Cloud Code for VS Code</b></a> — Google’s extension that helps to write, deploy and debug cloud-native applications quickly and easily. It allows deploying code to kubernetes and <a href="https://github.com/GoogleCloudPlatform/cloud-code-samples">supports 5 programming languages</a>.</p></li><li><p><a href="https://marketplace.visualstudio.com/items?itemName=eg2.vscode-npm-script">Npm support</a> for VS Code</p></li><li><p><a href="https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker">Code Spell Checker</a></p></li><li><p><a href="https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint">Markdownlint</a> — Improves the quality of markdown files. </p></li><li><p><a href="https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens">Gitlens</a> — Shows the history of code commits along with other relevant useful information.</p></li><li><p><a href="https://marketplace.visualstudio.com/items?itemName=IBM.output-colorizer">Output colorizer</a> — Colors the output of various commands. Helpful when observing application logs and other info in the IDE.</p></li><li><p><a href="https://marketplace.visualstudio.com/items?itemName=vscode-icons-team.vscode-icons">Vscode-icons</a> — Changes icons to known file extensions for better visibility and discoverability of the files.</p></li><li><p><a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-docker">Docker</a> — Manages docker images, autocompletion for docker files and more</p></li><li><p><a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-typescript-tslint-plugin">TSLint</a> — Linting for typescript (optional)</p></li><li><p><a href="https://marketplace.visualstudio.com/items?itemName=CoenraadS.bracket-pair-colorizer">Bracket pair colorizer</a> (optional)</p></li><li><p><a href="https://marketplace.visualstudio.com/items?itemName=christian-kohler.npm-intellisense">Npm intellisense</a> (optional)</p></li><li><p><a href="http://dbaeumer.vscode-eslint">ESLint Javascript</a> (optional)</p></li></ul><h2>Hello World in Dev Container on Pixelbook</h2><p>Let’s try to build, debug and deploy the <a href="https://github.com/GoogleCloudPlatform/cloud-code-samples/tree/master/nodejs/nodejs-hello-world">sample Hello World nodejs</a> app on Pixelbook using the remote dev container setup we just created:</p><ul><li><p><a href="https://cloud.google.com/sdk/docs/initializing">Initialize gcloud</a> by running <b>gcloud init</b> in a command line of your Pixelbook and following the steps. As part of our earlier setup, when we open the code in a remote container, Gcloud settings will be sync’ed into a dev container, so you won’t need to re-initialize every time.</p></li><li><p><a href="https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-access-for-kubectl">Connect to a GKE cluster</a> using the command below. We will use it to deploy our app. This also can be done outside of the dev container and will be sync’ed using our earlier setup in .devsettings.</p></li></ul></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><ul><li><p><b>Open the code in dev container</b>: In VS Code command palette, type: Remote-Containers: Open Folder in Container… and select your code location. The code will open in dev container, pre-configured with all the toolset and ready to go!</p></li></ul><ul><li><p><b>Build and deploy the code to GKE using Cloud Code</b>: In VS Code Command Palette, type: <b>Cloud Code: Deploy</b> and <a href="https://cloud.google.com/code/docs/vscode/deploying-an-application">follow the instructions</a>. Cloud Code will build the code, package it into container image, push it into container registry, then deploy it into GKE cluster we initialized earlier—all from the dev container on a Pixelbook!</p></li></ul><p>Though slick and small, the Pixelbook might just fit your developer needs. With VS Code, Remote development extension, Docker, Kubernetes and Cloud Code you can lift your development setup to the next level, where there is no need to worry about machine-specific or platform-specific differences affecting your productivity. By sharing dev container setup on Github, developers that clone your code will be able to reopen it in a container (assuming they have the Remote - Containers extension installed).</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="Cloud Code Deploy.gif" src="https://storage.googleapis.com/gweb-cloudblog-publish/original_images/Cloud_Code_Deploy.gif"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Once done, developers will get an isolated environment with all dependencies baked in — just start coding!</p><p>If you have a Pixelbook — or if you don’t, and just want to try out Cloud Code — the Hello World app and all config files are available on <a href="https://github.com/GoogleCloudPlatform/cloud-code-samples">GitHub</a>. <a href="https://twitter.com/simon_zeltser">Let me know</a> how it went and what your favorite setup for developer productivity is.</p><h2>Further reading</h2><ul><li><p><a href="https://support.google.com/chromebook/answer/9145439?hl=en">Set up Linux (Beta) on your Chromebook</a></p></li><li><p><a href="https://chromeos-cookbooks.firebaseapp.com/setup.html">Chromebook Developer Toolbox</a></p></li><li><p><a href="https://cloud.google.com/code/docs/vscode/quickstart">Getting Started with Cloud Code for VS Code</a></p></li><li><p><a href="https://github.com/GoogleCloudPlatform/cloud-code-samples">Cloud Code Templates Repo</a></p></li><li><p><a href="https://code.visualstudio.com/docs/remote/containers#_getting-started">Developing inside a Container</a></p></li></ul></div></div></body></html>Tue, 03 Sep 2019 14:00:00 -0000https://cloud.google.com/blog/products/application-development/build-a-dev-workflow-with-cloud-code-on-a-pixelbook/Google Cloud PlatformChrome EnterpriseApplication DevelopmentarticleBuild a dev workflow with Cloud Code on a PixelbookCan you use a Pixelbook for serious software development? Developer Programs Engineer Simon Zeltser shows you how.https://storage.googleapis.com/gweb-cloudblog-publish/images/Cloud_Code.max-600x600.jpgGooglehttps://cloud.google.com/blog/products/application-development/build-a-dev-workflow-with-cloud-code-on-a-pixelbook/Simon ZeltserDeveloper Programs EngineerBeyond the Map: Solving problems and powering location-based services with imageryhttps://cloud.google.com/blog/products/maps-platform/beyond-map-solving-problems-and-powering-location-based-services-imagery/<html><head></head><body><div class="block-paragraph"><div class="rich-text"><p><i>Editor’s Note: Product director Ethan Russell brings us the second installment of our Beyond the Map series. In today’s post, he’ll explain how we use imagery to overcome different mapping challenges around the world to help power businesses with location-based data and insights. For a look at how we use imagery to build our consumer Maps products, tune into the <a href="https://www.blog.google/products/maps/">Google Keyword blog</a> soon. <br/></i><i><br/></i>So far in this series <a href="https://cloud.google.com/blog/products/maps-platform/beyond-the-map-how-we-build-the-maps-that-power-your-apps-and-business">we’ve explained</a>, at a high level, how we combine imagery, third-party authoritative data, machine learning, and community contributions to continuously map the changing world. But what do we do when one of these key elements is missing, like authoritative data sources? Or when a city is growing so fast that traditional map making isn’t an option? Or when streets are so narrow, we can’t drive a Street View car through to map them? We run into endless mapping challenges in our tireless pursuit to map the world, but the one constant is that imagery is almost always the foundation of the solution. <br/><br/><b>Mapping growing cities from imagery <br/></b>Some areas of the world simply don't have basic roads and buildings mapped yet, which means we can’t reference basic mapping information from authoritative data sources like local governments and organizations. In these cases we build the map literally from the ground up, starting with imagery from which we can extract mapping data. There are broadly two kinds of imagery that we use. Overhead imagery from satellites and airplanes shows roads and buildings, while street-level imagery lets us see road names, road signs, building numbers and business names. In last month’s post, we touched on how we use machine learning to automatically extract information from imagery and keep maps data up to date for our customers. Let’s take a look at how this served as the foundation for significant improvements of our maps of Lagos, Nigeria and what that means for a local business using Google Maps Platform. <br/><br/>Once we had the necessary imagery of the area, we were able to use a number of our machine learning-based pipelines to quickly update the major components of the map within just a few months (traditional mapping processes can often take far longer). We focused on three deep-learning based approaches: drawing the outlines of buildings, identifying house numbers, and recognizing businesses. We created detailed outlines of buildings using a model trained not only on the per-pixel details of what constitutes a building, but also on the high-level traits of building shapes seen in the overhead imagery. To identify house numbers and businesses, we used three-part detection, classification, and extraction approaches based on the continuation of work discussed in <a href="https://arxiv.org/abs/1704.03549">this paper</a>. These two algorithms were fed high-resolution Street View imagery as input. The accurate positioning of these images in six degrees of freedom was critical to getting the position of the house or business exactly right. As a result, we were able to improve the quality of our map data in Lagos in about one year (from 2017 to 2018) to levels equivalent to countries where we've spent many years building the maps. </p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="Visualization of improved maps data in Lagos, Nigeria" src="https://storage.googleapis.com/gweb-cloudblog-publish/original_images/lagos.gif"/><figcaption class="article-image__caption "><div class="rich-text">Improved coverage of buildings (pink) and points of interest (green) in Lagos, Nigeria from 2012 to 2018</div></figcaption></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>For many people, an incorrect address when trying to find a business or other location is just a small nuisance. But for businesses, it could mean loss of business. And for <a href="http://lifebank.ng/">Lifebank</a>, a company that connects blood suppliers to hospital patients in Lagos, it could be a matter of life and death. In 2016, founder Temie Giwa-Tubosun, used Google Maps Platform to create and map an online blood repository in partnership with 52 blood banks across Lagos allowing doctors to request a blood type and immediately access a map that tracks the journey of the delivery. </p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="LifeBank's life-saving app" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/54a218c9-1593-448d-ad9f-e6b507ee1da1_1.max-1000x1000.JPG"/><figcaption class="article-image__caption "><div class="rich-text">The LifeBank app helps connect blood banks, doctors, and drivers across Lagos, Nigeria</div></figcaption></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Before LifeBank, finding and delivering blood to a patient in Lagos could take several hours and in some cases, several days. But LifeBank changed that by transporting blood in an average of 45 minutes from initial request to final delivery. The team has registered over 5,800 blood donors, moved over 15,000 units, served 300-plus hospitals, and saved more than 4,000 lives. For Temie, access to mapping information was an important part of solving the blood crisis problem in her native Nigeria.<br/><br/><b>Mapping narrow roads with Street View 3-wheelers<br/></b>Places like Indonesia have some roads that are too narrow for cars, but just right for the 2-wheelers that are commonly used in the country. We needed to map these roads in order to introduce 2-wheeler navigation in Google Maps and provide 2-wheeler navigation solutions to our ridesharing customers, but our Street View cars were too big. Instead, we mounted a Trekker to a 3-wheeler–taking into account both operator safety and local regulations in our vehicle choice–and started mapping the narrow streets. </p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--medium h-c-grid__col h-c-grid__col--4 h-c-grid__col--offset-4 "><img alt="Street View 3-wheeler" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/IMG_20180325_162930.max-1000x1000.jpg"/><figcaption class="article-image__caption "><div class="rich-text"><p>A “Street View 3-wheeler” used to map narrow roads in Indonesia</p></div></figcaption></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>The solution makes mapping projects in places off the beaten path or areas that might be inaccessible to cars possible and scalable. It enabled us to capture the street-level imagery of narrow roads needed to launch 2-wheeler navigation in Indonesia and improve our maps of the area. Since we’ve launched in Indonesia, we’ve brought 2-wheeler navigation to 21 other countries. </p><p></p><p>As you can see, imagery really is the foundation for our maps and solving map making problems worldwide. But this is just a look at a couple of the challenges we’ve solved with imagery. It’s an incredible resource for learning about the world and we have lots of creative ways of collecting and using imagery to help people explore and help businesses to build and expand their services–even in hard to map areas. Come back to the Google Maps Platform blog next time for another installment of Beyond the Map. Until then, to learn more about Google Maps Platform, <a href="https://cloud.google.com/maps-platform/">visit our website</a>.</p></div></div></body></html>Fri, 30 Aug 2019 16:00:00 -0000https://cloud.google.com/blog/products/maps-platform/beyond-map-solving-problems-and-powering-location-based-services-imagery/Google Maps PlatformarticleBeyond the Map: Solving problems and powering location-based services with imageryThe second installment of our Beyond the Map series explains how we use imagery to overcome different mapping challenges around the world to help power businesses with location-based data and insights.https://storage.googleapis.com/gweb-cloudblog-publish/images/AerialBuildings.max-600x600.jpgGooglehttps://cloud.google.com/blog/products/maps-platform/beyond-map-solving-problems-and-powering-location-based-services-imagery/Ethan RussellProduct DirectorKubernetes security audit: What GKE and Anthos users need to knowhttps://cloud.google.com/blog/products/containers-kubernetes/kubernetes-security-audit-what-gke-and-anthos-users-need-to-know/<html><head></head><body><div class="block-paragraph"><div class="rich-text"><p>Kubernetes reached an important milestone recently: the publication of its first-ever security audit! Sponsored by the Cloud Native Computing Foundation (CNCF), this security audit reinforces what has been apparent to us for some time now: Kubernetes is a mature open-source project for organizations to use as their infrastructure foundation.</p><p>While every audit will uncover something, this report only found a relatively small number of significant vulnerabilities that need to be addressed. “Despite many important findings, we did not see fundamental architectural design flaws, or critical vulnerabilities that should cause pause when adopting Kubernetes for high-security workloads or critical business functions,” <a href="https://www.helpnetsecurity.com/2019/08/12/kubernetes-security-matures/">said</a> Aaron Small, Product Manager, Google Cloud and member of the Security Audit Working Group. Further, Kubernetes has an <a href="https://kubernetes.io/docs/reference/issues-security/security/">established vulnerability reporting, response, and disclosure process</a>, which is staffed with senior developers who can triage and take action on issues.</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="GCP_k8_securityaudit.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/GCP_k8_securityaudit.0480025209600258.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Performing this security audit was a big effort on behalf of the CNCF, which has a mandate to improve the security of its projects via its <a href="https://bestpractices.coreinfrastructure.org/en">Best Practices Badge Program</a>. To take Kubernetes through this first security audit, the Kubernetes Steering Committee formed a working group, developed an RFP, worked with vendors, reviewed and then finally published the report. You can get your hands on the <a href="https://github.com/kubernetes/community/blob/master/wg-security-audit/findings/Kubernetes%20Final%20Report.pdf">full report</a> on the Working Group’s GitHub page, or read the <a href="https://www.cncf.io/blog/2019/08/06/open-sourcing-the-kubernetes-security-audit/">highlights in the CNCF blog post</a>.</p><h2>Kubernetes security for GKE and Anthos users</h2><p>Clocking in at 241 pages, the final report is very thorough and interesting and we encourage you to read it. But what if you’re just interested in what this report means for Google Cloud’s managed platforms, <a href="https://cloud.google.com/kubernetes-engine/">Google Kubernetes Engine</a> (GKE) and <a href="https://cloud.google.com/anthos/">Anthos</a>? If you’re not going to read the whole thing, here’s the gist of the report and takeaways for Google Cloud customers.</p><p><b>GKE makes it easy for you to follow recommended configurations<br/></b>The report lays out a <a href="https://github.com/kubernetes/community/blob/master/wg-security-audit/findings/Kubernetes%20White%20Paper.pdf">list of recommended actions for cluster administrators</a>, including using RBAC, applying a Network Policy, and limiting access to logs which may contain sensitive information. The report also calls out Kubernetes’ default settings. In GKE, we’ve been actively changing these over time, including turning off ABAC and basic authentication by default, to make sure new clusters you create are more secure. To apply the recommended configurations in GKE, and see which have already been applied for you, check out the <a href="https://cloud.google.com/kubernetes-engine/docs/how-to/hardening-your-cluster">GKE hardening guide</a>.</p><p><b>It’s not all up to you <br/></b>The <a href="https://github.com/kubernetes/community/blob/master/wg-security-audit/findings/Kubernetes%20Threat%20Model.pdf">threat model</a> assessed the security posture of eight major components, but because of the GKE <a href="https://cloud.google.com/blog/products/containers-kubernetes/exploring-container-security-the-shared-responsibility-model-in-gke-container-security-shared-responsibility-model-gke">shared responsibility model</a>, you don’t have to worry about all of them. GKE is responsible for providing updates to vulnerabilities for the eight components listed in the report, while you as the user are responsible for upgrading nodes and configuration related to workloads. You don’t even need to upgrade nodes if you leave node auto-upgrade enabled. </p><p><b>Kubernetes and GKE security are only going to keep getting better<br/></b>With more eyes on this shared, open source technology, more well-hidden bugs are likely to be found and remediated. The Kubernetes community dedicated significant time and resources to this audit, emphasizing that security is truly a top priority. With open audits like the one performed by the CNCF, it’s easier for researchers—or your team—to understand the real threats, and spend their time further researching or remediating the most complex issues. </p><p>And when issues do arise, as we’ve seen multiple times with recent vulnerabilities, the upstream <a href="https://github.com/kubernetes/security/blob/master/security-release-process.md#product-security-committee-psc">Kubernetes Product Security Committee</a> is on top of it, quickly responding and providing fixes to the community. </p><p>Finally, since GKE is an official distribution, we pick up patches as they become available in Kubernetes and make them available automatically for the control plane, master, and node. Masters are automatically upgraded and patched, and if you have node auto-upgrade enabled, your node patches will be automatically applied too. You can track the progress to address the vulnerabilities surfaced by this report in the <a href="https://github.com/kubernetes/kubernetes/issues/81146">issue dashboard</a>.</p><p>If you want to dig in deeper, check out the full <a href="https://github.com/kubernetes/community/blob/master/wg-security-audit/findings/Kubernetes%20Final%20Report.pdf">report</a>, available on GitHub. Thanks again to the Kubernetes Security Audit Working Group, the CNCF, Trail of Bits and Atredis Partners for the amazing work they did to complete this in-depth assessment! To learn more about trends in container security here at Google Cloud, be sure to follow our <a href="https://cloud.google.com/blog/topics/exploring-container-security">Exploring container security</a> blog series.</p></div></div></body></html>Fri, 30 Aug 2019 13:00:00 -0000https://cloud.google.com/blog/products/containers-kubernetes/kubernetes-security-audit-what-gke-and-anthos-users-need-to-know/Identity & SecurityGoogle Cloud PlatformGKEAnthosHybrid CloudContainers & KubernetesarticleKubernetes security audit: What GKE and Anthos users need to knowRead about the implications of the first Kubernetes security audit on GKE and Anthos.https://storage.googleapis.com/gweb-cloudblog-publish/images/Exploring_container_security.max-600x600.pngGooglehttps://cloud.google.com/blog/products/containers-kubernetes/kubernetes-security-audit-what-gke-and-anthos-users-need-to-know/Maya KaczorowskiProduct Manager, Container securityHow to quickly solve machine learning forecasting problems using Pandas and BigQueryhttps://cloud.google.com/blog/products/ai-machine-learning/how-to-quickly-solve-machine-learning-forecasting-problems-using-pandas-and-bigquery/<html><head></head><body><div class="block-paragraph"><div class="rich-text"><p>Time-series forecasting problems are ubiquitous throughout the business world. For example, you may want to predict the probability that some event will happen in the future or forecast how many units of a product you’ll sell over the next six months. Forecasting like this can be posed as a supervised machine learning problem. </p><p>Like many machine learning problems, the most time-consuming part of forecasting can be setting up the problem, constructing the input, and feature engineering. Once you have created the features and labels that come out of this process, you are ready to train your model.</p><p>A common approach to creating features and labels is to use a sliding window where the features are historical entries and the label(s) represent entries in the future. As any data-scientist that works with time-series knows, this sliding window approach can be tricky to get right.</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="1_sliding window on an example dataset.gif" src="https://storage.googleapis.com/gweb-cloudblog-publish/original_images/1_sliding_window_on_an_example_dataset.gif"/><figcaption class="article-image__caption "><div class="rich-text"><i>A sliding window on an example dataset. Each window represents a feature vector for the dataset and the label(s) is one or more points in the future.</i></div></figcaption></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Below is a good workflow for tackling forecasting problems:</p><p>1. Create features and labels on a subsample of data using Pandas and train an initial model locally<br/>2. Create features and labels on the full dataset using BigQuery<br/>3. Utilize BigQuery ML to build a scalable machine learning model<br/>4. (Advanced) Build a forecasting model using Recurrent Neural Networks in Keras and TensorFlow</p><p>In the rest of this blog, we’ll use an example to provide more detail into how to build a forecasting model using the above workflow. (The code is available on <a href="https://aihub.cloud.google.com/u/0/p/products%2F167a3129-a605-49eb-9f51-c9b32984c0b6">AI Hub</a>)</p><h2>First, train locally</h2><p>Machine learning is all about running experiments. The faster you can run experiments, the more quickly you can get feedback, and thus the faster you can get to a Minimum Viable Model (MVM). It’s beneficial, then, to first work on a subsample of your dataset and train locally before scaling out your model using the entire dataset.</p><p>Let’s build a model to forecast the median housing price week-by-week for New York City. We spun up a <a href="http://console.cloud.google.com/mlengine/notebooks">Deep Learning VM</a> on Cloud AI Platform and loaded our data from <a href="https://www1.nyc.gov/site/finance/taxes/property-annualized-sales-update.page">nyc.gov</a> into BigQuery. Our dataset goes back to 2003, but for now let’s just use prices beginning 2011.</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="2_median housing price.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/2_median_housing_price.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Since our goal is to forecast future prices, let's create sliding windows that accumulate historical prices (features) and a future price (label). Our source table contains date and median price:</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--small h-c-grid__col h-c-grid__col--2 h-c-grid__col--offset-5 "><img alt="3_forecast future prices.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/3_forecast_future_prices.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Here is the entire dataset plotted over time:</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="4_entire dataset plotted.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/4_entire_dataset_plotted.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>To create our features, we’ll pick a historical window size—e.g., one year—that will be used to forecast the median home price in six months. To do this, we have implemented a reusable function based on Pandas that allows you to easily generate time-series features and labels. Feel free to use this function on your own dataset.</p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><p>After running <code>create_rolling_features_label</code>, a feature vector of length 52 (plus the date features) is created for each example, representing the features before the prediction date.</p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="5_create_rolling_features_label.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/5_create_rolling_features_label.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>This can be shown with a rolling window:</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="6_rolling window.gif" src="https://storage.googleapis.com/gweb-cloudblog-publish/original_images/6_rolling_window.gif"/><figcaption class="article-image__caption "><div class="rich-text"><i>The create_rolling_features_label function creates windows for the feature and label. In this case, the features consist of 52 weeks and the label consists of a week 6 months into the future.</i></div></figcaption></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Once we have the features and labels, the next step is to create a training and test set. In time-series problems, it’s important to split them temporally so that you are not leaking future information that would not be available at test time into the trained model.</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="7_training and test set.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/7_training_and_test_set.max-1000x1000.png"/><figcaption class="article-image__caption "><div class="rich-text"><i>The training set (blue) will consist of data where the label occurs before the split date (2015-12-30'), while the test set (green) consists of rows where the label is after this date.</i></div></figcaption></figure></div></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><p>In practice, you may want to scale your data using z-normalization or detrend your data to reduce seasonality effects. It may help to utilize differencing, as well to remove trend information. Now that we have features and labels, this simply becomes a traditional supervised learning problem, and you can use your favorite ML library to train a model. Here is a simple example using sklearn:</p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><h2>Scale our model</h2><p>Let's imagine we want to put our model into production and automatically run it every week, using batch jobs, to get a better idea of future sales.Let’s also imagine we may want to forecast a model day-by-day.</p><p>Our data is stored in BigQuery, so let’s use the same logic that we used in Pandas to create features and labels, but instead run it at scale using BigQuery. We have developed a generalized Python function that creates a SQL string that lets you do this with BigQuery:</p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><p>We pass the table name that contains our data, the value name that we are interested in, the window size (which is the input sequence length), the horizon of how far ahead in time we skip between our features and our labels, and the labels_size (which is the output sequence length). Labels size is equal to 1 here because, for now, we are only modeling sequence-to-one—even though this data pipeline can handle sequence-to-sequence. Feel free to write your own sequence-to-sequence model to take full advantage of the data pipeline!</p>We can then execute the SQL string <code>scalable_time_series</code> in BigQuery. A sample of the output shows that each row is a different sequence. For each sequence, we can see the time ranges of the features and the labels. For the features, the timespan is 52 weeks, which is the <code>window_size</code>, and for labels it is one day, which is the <code>labels_size</code>.</div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="8_scalable_time_series.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/8_scalable_time_series.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Looking at the same sampled rows, we can see how the training data is laid out. We have a column for each timestep of the previous price, starting with the farthest back in time on the left  and moving forward. The last column is the label, the price one week ahead.</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="9_price one week ahead.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/9_price_one_week_ahead.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Now we have our data, ready for training, in a BigQuery table. Let’s take advantage of <a href="https://cloud.google.com/bigquery-ml/docs/bigqueryml-intro">BigQuery ML</a> and build a forecasting model using SQL.</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="10_forecasting model using SQL.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/10_forecasting_model_using_SQL.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Above we are creating a linear regression model using our 52 past price features and predicting our label <code>price_ahead_1</code>. This will create a BQML MODEL in our <code>bqml_forecasting</code> dataset.</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="11_52 past price features.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/11_52_past_price_features.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>We can check how our model performed by calling <code>TRAINING_INFO</code>. This shows the training run index, iteration index, the training and eval loss at each iteration, the duration of the iteration, and the iteration's learning rate. Our model is training well since the eval loss is continually getting smaller for each iteration.</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="12_TRAINING_INFO.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/12_TRAINING_INFO.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>We can also do an evaluation of our trained model by calling <code>EVALUATE</code>. This will show common evaluation metrics that we can use to compare our model with other models to find the best choice among all of our options.</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="13_EVALUATE.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/13_EVALUATE.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Lastly, machine learning is all about prediction. The training is just a means to an end. We can get our predictions by using the above query, where we have prepended predicted_ to the name of our label.</p><p>Now, let’s imagine that we want to run this model every week. We can easily create a batch job that is automatically executed using a <a href="https://cloud.google.com/bigquery/docs/scheduling-queries">scheduled query</a>.</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="14_scheduled query.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/14_scheduled_query.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Of course, if we want to build a more custom model, we can use TensorFlow or another machine library, while using this same data engineering approach to create our features and labels to be read into our custom machine learning model. This technique could possibly improve performance.</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="15_custom machine learning model.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/15_custom_machine_learning_model.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>To use an ML framework like TensorFlow, we'll need to write the model code and also get our data in the right format to be read into our model. We can make a slight modification to the previous query we used for BigQuery ML so that the data will be amenable to the CSV file format. </p><p>For this example, imagine you wanted to build a sequence-to-sequence model in TensorFlow that can handle variable length features. One approach to achieve this would be to aggregate all the features into a single column named <code>med_sales_price_agg</code>, separated by semicolons. The features (if we have more than just this feature in the future) and the label are all separated by a comma.</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="16_med_sales_price_agg.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/16_med_sales_price_agg.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>We'll execute the query in BigQuery and will make a table for train and eval. This will then get exported to CSV files in Cloud Storage. The diagram above is what one of the exported CSV files looks like—at least the header and the first line—with some comments added. Then when reading the data into our model using <a href="https://www.tensorflow.org/api_docs/python/tf/data">tf.data</a>, we will specify the delimiter pattern shown above to correctly parse the data.</p><p>Please check out our <a href="https://aihub.cloud.google.com/u/0/p/products%2F167a3129-a605-49eb-9f51-c9b32984c0b6">notebook</a> on AI Hub for an end-to-end example showing how this would work in practice and how to submit a training job on Google Cloud AI Platform. For model serving, the model can deployed on AI Platform or it can <a href="https://cloud.google.com/bigquery-ml/docs/making-predictions-with-imported-tensorflow-models">be deployed directly in BigQuery</a>. </p><h2>Conclusion</h2><p>That's it! The workflow we shared will allow you to automatically and quickly setup any time-series forecasting problem. Of course, this framework can also be adapted for a classification problem, like using a customer’s historical behavior to predict the probability of churn or to identify anomalous behavior over time. Regardless of the model you build, these approaches let you quickly build an initial model locally, then scale to the cloud using BigQuery.</p><p><i>Learn more about <a href="https://cloud.google.com/bigquery/">BigQuery</a> and <a href="https://cloud.google.com/ai-platform/">AI Platform</a>.</i></p></div></div></body></html>Fri, 30 Aug 2019 13:00:00 -0000https://cloud.google.com/blog/products/ai-machine-learning/how-to-quickly-solve-machine-learning-forecasting-problems-using-pandas-and-bigquery/BigQueryGoogle Cloud PlatformAI & Machine LearningarticleHow to quickly solve machine learning forecasting problems using Pandas and BigQueryLearn how to quickly solve machine learning forecasting problems using Pandas, BigQuery, and Google Cloud AI Platformhttps://storage.googleapis.com/gweb-cloudblog-publish/images/DataAnalytics.max-600x600.pngGooglehttps://cloud.google.com/blog/products/ai-machine-learning/how-to-quickly-solve-machine-learning-forecasting-problems-using-pandas-and-bigquery/Chris RawlesML Solutions EngineerRyan GillardMachine Learning Solutions EngineerExpanding your patent set with ML and BigQueryhttps://cloud.google.com/blog/products/data-analytics/expanding-your-patent-set-with-ml-and-bigquery/<html><head></head><body><div class="block-paragraph"><div class="rich-text"><p>Patents protect unique ideas and intellectual property. Patent landscaping is an analytical approach commonly used by corporations, patent offices, and academics to better understand the potential technical coverage of a large number of patents where manual review (i.e., actually reading the patents) is not feasible due to time or cost constraints. Luckily, patents contain rich information, including metadata (examiner-supplied classification codes, citations, dates, and information about the patent applicant), images, and thousands of words of descriptive text, which enable the use of more advanced methodological techniques to augment manual review.</p><p>Patent landscaping techniques have improved as machine learning models have increased practitioners’ ability to analyze all this data. Here on Google’s Global Patents Team, we’ve developed a new patent landscaping methodology that uses Python and BigQuery on Google Cloud to allow you to easily access patent data and generate automated landscapes.</p><p>There are some important concepts to know as you’re getting started with patent landscaping. Machine learning (ML) landscaping methods that use these sources of information generally fall into one of two categories:  </p><ul><li><b>Unsupervised</b>: Given a portfolio of patents about which the user knows no prior information, then utilize an unsupervised algorithm to generate topic clusters to provide users a better high-level overview of what that portfolio contains.</li><li><b>Supervised</b>: Given a seed set of patents about which the user is confident covers a specific technology, then identify other patents among a given set that are likely to relate to the same technology. </li></ul><p>The focus of this post is on supervised patent landscaping, which tends to have more impact and be commonly used across industries, such as:</p><ul><li><p><b>Corporations</b> that have highly curated seed sets of patents that they own and wish to identify patents with similar technical coverage owned by other entities. That may aid various strategic initiatives, including targeted acquisitions and cross-licensing discussions. </p></li><li><p><b>Patent offices</b> that regularly perform statistical analyses of filing trends in emerging technologies (like AI) for which the existing classification codes are not sufficiently nuanced. </p></li><li><p><b>Academics</b> who are interested in understanding how economic policy impacts patent filing trends in specific technology areas across industries. </p></li></ul><p>Whereas landscaping methods have historically relied on keyword searching and Boolean logic applied to the metadata, supervised landscaping methodologies are increasingly using advanced ML techniques to extract meaning from the actual full text of the patent, which contains far richer descriptive information than the metadata. Despite this recent progress, most supervised patent landscaping methodologies face at least one of these challenges:</p><ul><li><p>Lack of confidence scoring: Many approaches simply return a list of patents without indication of which are the most likely to actually be relevant to a specific technology space covered in the seed set. This means that a manual reviewer can’t prioritize the results for manual review, which is a common use of supervised landscapes. </p></li><li><p>Speed: Many approaches that use more advanced machine learning techniques are extremely slow, making them difficult to use on-demand. </p></li><li><p>Cost: Most existing tools are provided by for-profit companies that charge per analysis or as a recurring SaaS model, which is cost-prohibitive for many users. </p></li><li><p>Transparency: Most available approaches are proprietary, so the user cannot actually review the code or have full visibility into the methodologies and data inputs. </p></li><li><p>Lack of clustering: Many technology areas comprise multiple sub-categories that require a clustering routine to identify. Clustering the input set could formally group the sub-categories in a formulaic way that any downstream tasks could then make use of to more effectively rank and return results. Few (if any) existing approaches attempt to discern sub-categories within the seed set. </p></li></ul><p>The new patent landscaping methodology we’ve developed satisfies all of the common shortcomings listed above. This methodology uses Colab (Python) and GCP (BigQuery) to provide the following benefits:</p><ul><li><p>Fully transparent with all code and data publicly available, and provides confidence scoring of all results</p></li><li><p>Clusters patent data to capture variance within the seed set</p></li><li><p>Inexpensive, with sole costs incurring from GCP compute fee</p></li><li><p>Fast, hundreds or thousands of patents can be used as input with results returned in a few minutes</p></li></ul><p>Read on for a high-level overview of the methodology with code snippets. The complete code is found <a href="https://github.com/google/patents-public-data/blob/master/examples/patent_set_expansion.ipynb">here</a>, and can be reused and modified for your own ML and BigQuery projects. Finally, if you need an introduction to the <a href="https://console.cloud.google.com/marketplace/details/google_patents_public_datasets/google-patents-public-data">Google Public Patents Datasets</a>, a great overview is found <a href="https://cloud.google.com/blog/big-data/2017/10/google-patents-public-datasets-connecting-public-paid-and-private-patent-data">here</a>.</p><h2>Getting started with the patent landscaping methodology </h2><p><b>1. Select a seed set and a patent representation<br/></b>Generating a landscape first requires a seed set to be used as a starting point for the search. In order to produce a high-quality search, the input patents should themselves be closely related. More closely related seed sets tend to generate landscapes more tightly clustered around the same technical coverage, while a set of completely random patents will likely yield noisy and more uncertain results.</p><p>The input set could span a <a href="https://www.uspto.gov/web/patents/classification/cpc.html">Cooperative Patent Code (CPC)</a>, a technology, an assignee, an inventor, etc., or a specific list of patents covering some known technological area. In this walkthrough a term (word) is used to find a seed set. In the <a href="https://console.cloud.google.com/marketplace/details/google_patents_public_datasets/google-patents-public-data">Google Patents Public Datasets</a>, there is a “top terms” field available for all patents in the “google_patents_research.publications” table. The field contains 10 of the most important terms used in a patent. The terms can be unigrams (such as “aeroelastic,” “genotyping,” or “engine”) or bi-grams (such as “electrical circuit,” “background noise,” or “thermal conductivity”).</p><p>With a seed set selected, you’ll next need a representation of a patent suitable to be passed through an algorithm. Rather than using the entire text of a patent or discrete features of a patent, it’s more consumable to use an embedding for each patent. <a href="https://en.wikipedia.org/wiki/Word_embedding">Embeddings</a> are a learned representation of a data input through some type of model, often with a neural network architecture. They reduce the dimensionality of an input set by mapping the most important features of the inputs to a vector of continuous numbers. A benefit of using embeddings is the ability to calculate distances between them, since several distance measures between vectors exist.</p><p>You can find a set of patent embeddings in BigQuery. The patent embeddings were built using a machine learning model that predicted a patent's CPC code from its text. Therefore, the learned embeddings are a vector of 64 continuous numbers intended to encode the information in a patent's text. Distances between the embeddings can then be calculated and used as a measure of similarity between two patents. </p><p>In the following example query (performed in BigQuery), we’ve selected a random set of U.S. patents (and collected their embeddings) granted after Jan. 1, 2005, with a top term of "neural network."</p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><p><b>2. Organize the seed set<br/></b>With the input set determined and the embedding representations retrieved, you have a few options for determining similarity to the seed set of patents.</p><p>Let’s go through each of the options in more detail.</p><p>1. Calculating an overall embedding point—centroid, medoid, etc.— for the entire input set and performing similarity to that value. Under this method, one metric is calculated to represent the entire input set. That means that the input set of embeddings, which could contain information on hundreds or thousands of patents, ends up pared down to a single point. </p><p>There are drawbacks to any methodology that is dependent on one point. If the value itself is not well-selected, all results from the search will be poor. Furthermore, even if the point is well-selected, the search depends on only that one embedding point, meaning all search results may represent the same area of a topic, technology, etc. By reducing the entire set of inputs to one point, you’ll lose significant information about the input set.</p><p>2. Seed set x N similarity, e.g., calculating similarity to all patents in the input set to all other patents. Doing it this way means you apply the vector distance metric used between each patent in the input set and all other patents in existence. This method presents a few issues: </p><ul><li><p>Lack of tractability. Calculating similarity for (seed_set_size x all_patents) is an expensive solution in terms of time and compute. </p></li><li><p>Outliers in the input set are treated as equals to highly representative patents.</p></li><li><p>Dense areas around a single point could be overrepresented in the results.</p></li><li><p>Reusing the input points for similarity may fail to expand the input space.</p></li></ul><p>3. Clustering the input set and performing similarity to a cluster. We recommend clustering as the preferred approach to this problem, as it will overcome many of the issues presented by the other two methods. Using clustering, information about the seed set will be condensed into multiple representative points, with no point being an exact replica of its input. With multiple representative points, you can capture various parts of the input technology, features, etc. </p><p><b>3. Cluster the seed set<br/></b>A couple of notes about the embeddings on BigQuery:</p><ul><li><p>The embeddings are a vector of 64 numbers, meaning that data is high-dimensional.</p></li><li><p>As noted earlier, the embeddings were trained in a prediction task, not explicitly trained to capture the "distance" (difference) between patents.</p></li></ul><p>Based on the embedding training, the clustering algorithm needs to be able to effectively handle clusters of varying density. Since the embeddings were not trained to separate patents evenly, there will be areas of the embedding space that are more or less dense than others, yet represent similar information between documents.</p><p>Furthermore, with high-dimensional data, distance measures can degrade rapidly. One possible approach to overcoming the dimensionality is to use a secondary metric to represent the notion of distance. Rather than using absolute distance values, it’s been shown that a ranking of data points from their distances (and removing the importance of the distance magnitudes) will produce more stable results with higher dimensional data. So our clustering algorithm should remove sole dependence on absolute distance.</p><p>It’s also important that a clustering method be able to detect outliers. When providing a large set of input patents, you can expect that not all documents in the set will be reduced to a clear sub-grouping. When the clustering algorithm is unable to group data in a space, it should be capable of ignoring those documents and spaces. </p><p>Several clustering algorithms exist (<a href="https://en.wikipedia.org/wiki/Hierarchical_clustering">hierarchical</a>, <a href="https://en.wikipedia.org/wiki/Clique_percolation_method">clique-based</a>, <a href="https://hdbscan.readthedocs.io/en/latest/how_hdbscan_works.html">hdbscan</a>, etc.) that have the properties we require, any of which can be applied to this problem in place of the algorithm used here. In this application, we used the <a href="http://mlwiki.org/index.php/SNN_Clustering">shared nearest neighbor</a> (SNN) clustering method to determine the patent grouping. </p><p>SNN is a clustering method that evaluates the neighbors for each point in a dataset and compares the neighbors shared between points to find clusters. SNN is a useful clustering algorithm for determining clusters of varying density. It is good for high-dimensional data, since the explicit distance value is not used in its calculation; rather, it uses a ranking of neighborhood density. The complete clustering code is available in the <a href="https://github.com/google/patents-public-data/blob/master/examples/patent_set_expansion.ipynb">GitHub repo</a>.</p><p>For each cluster found, the SNN method determines a representative point for each cluster in order to perform a search against it. Two common approaches for representing geometric centers are centroids and medoids. The centroid simply takes the mean value from each of the 64 embedding dimensions. A medoid is the point in a cluster whose average dissimilarity to all objects in a cluster is minimized. In this walkthrough, we’re using the centroid method.</p><p>Below you’ll see a Python code snippet of the clustering application and calculations of some cluster characteristics, along with a visualization of the clustering results. The dimensions in the visualization were reduced using <a href="https://en.wikipedia.org/wiki/T-distributed_stochastic_neighbor_embedding">TSNE</a>, and outliers in the input set have grayed out. The results of the clustering can be seen by the like colors forming a cluster of patents:</p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--medium h-c-grid__col h-c-grid__col--4 h-c-grid__col--offset-4 "><img alt="Cluster the seed set.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/Cluster_the_seed_set.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p><b>4. Perform a similarity search<br/></b>Once the cluster groups and their centers have been determined, you’ll need a measure of similarity between vectors. Several measures exist, and you can implement any preferred measure. In this example, we used cosine distances to find the similarity between two vectors.</p><p>Using the <a href="https://en.wikipedia.org/wiki/Cosine_similarity">cosine distance</a>, the similarity between a cluster center is compared to all other patents using each of their embeddings. Distance values close to zero mean that the patent is very similar to the cluster point, whereas distances close to one are very far from the cluster point. You’ll see the resulting similarity calculations ordered for each cluster and get an upper bound number of assets.</p><p>Below you’ll see a Python code snippet that iterates through each cluster. For each cluster, a query is performed in BigQuery that calculates the cosine distance between the cluster center and all other patents, and returns the most similar results to that cluster, like this:</p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><p><b>5. Apply confidence scoring<br/></b>The previous step returns the most similar results to each cluster along with its cosine distance values. From here, the final step takes properties of the cluster and the distance measure from the similarity results to create a confidence level for each result. There are multiple ways to construct a confidence function, and each method may have benefits to certain datasets. </p><p>In this walkthrough, we do the confidence scoring using a half squash function. The half squash function is formulated as follows:</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--medium h-c-grid__col h-c-grid__col--4 h-c-grid__col--offset-4 "><img alt="confidence scoring.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/confidence_scoring.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>The function takes as input the cosine distance value found between a patent and a cluster center (x). Furthermore, the function requires two parameters that affect how the distances of the results are fit onto the confidence scale:</p><ol><li><p>A power variable, which defines the properties of the distribution showing the distance results—effectively the slope of the curve. In this version, a power of two is used.</p></li><li><p>A half value, which represents the midpoint of the curve returned and defines the saturation on either side of the curve. In this implementation, each cluster uses its own half value. The half value for each cluster is formulated as follows:<br/>(mean distance of input patents in cluster + 2 * standard deviation of input cluster distances)</p></li></ol><p>The confidence scoring function effectively re-saturates the returned distance values to a scale between [0,1], with an exponentially decreasing value as the distance between a patent and the cluster center grows:</p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><p><b>Results from this patent landscaping methodology<br/></b>Applying the confidence function for all of the similarity search results yields a distribution of patents by confidence score. At the highest levels of confidence, fewer results will appear. As you move down the confidence distribution, the number of results increases exponentially.</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="patent landscaping methodology.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/patent_landscaping_methodology.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Not all results returned are guaranteed to be high-quality; however, the higher the confidence level, the more likely a result is positive. Depending on the input set, the confidence levels will not necessarily begin at 99%. From the results above, using our “neural network” random patent set, the highest confidence results sit in the 60% to 70% range. From our own experimentation, the more tightly related the input set, the higher the confidence level in the results will be, since the clusters will be more compact.</p><p>This walkthrough provides one method for expanding a set of patents to generate a landscape. Several changes or improvements can be made to the cluster algorithm, distance calculations and confidence functions to suit any dataset. Explore the <a href="https://cloud.google.com/blog/products/gcp/google-patents-public-datasets-connecting-public-paid-and-private-patent-data">patents dataset for yourself</a>, and try out GitHub for the <a href="https://github.com/google/patents-public-data/blob/master/examples/patent_set_expansion.ipynb">patent set expansion code</a> too.</p></div></div></body></html>Fri, 30 Aug 2019 13:00:00 -0000https://cloud.google.com/blog/products/data-analytics/expanding-your-patent-set-with-ml-and-bigquery/Google Cloud PlatformBigQueryData AnalyticsarticleExpanding your patent set with ML and BigQueryYou can use BigQuery and Python to perform faster patent landscaping. Try it out with newly available code and the Google Patents Public Dataset.https://storage.googleapis.com/gweb-cloudblog-publish/images/Citrix-BlogHeader-r1_gSJYlNx.max-600x600.pngGooglehttps://cloud.google.com/blog/products/data-analytics/expanding-your-patent-set-with-ml-and-bigquery/Rob SrebrovicData Scientist, Global PatentsNew release of Cloud Storage Connector for Hadoop: Improving performance, throughput and morehttps://cloud.google.com/blog/products/data-analytics/new-release-of-cloud-storage-connector-for-hadoop-improving-performance-throughput-and-more/<html><head></head><body><div class="block-paragraph"><div class="rich-text"><p>We're pleased to announce a new version of the <a href="https://github.com/GoogleCloudPlatform/bigdata-interop/releases/tag/v2.0.0">Cloud Storage Connector for Hadoop</a> (also known as GCS Connector), which makes it even easier to substitute your Hadoop Distributed File System (HDFS) with Cloud Storage. This new release can give you increased throughput efficiency for columnar file formats such as Parquet and ORC, isolation for Cloud Storage directory modifications, and overall big data workload performance improvements, like lower latency, increased parallelization, and intelligent defaults.</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="Diagram 1.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/Diagram_1.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>The Cloud Storage Connector is an open source <a href="https://github.com/GoogleCloudPlatform/bigdata-interop/tree/v2.0.0/gcs">Java client library</a> that runs in Hadoop JVMs (like data nodes, mappers, reducers, Spark executors, and more) and allows your workloads to access Cloud Storage. The connector lets your big data open source software [such as Hadoop and Spark jobs, or the Hadoop Compatible File System (HCFS) CLI] read/write data directly to Cloud Storage, instead of to HDFS. Storing data in Cloud Storage has <a href="https://cloud.google.com/blog/products/storage-data-transfer/hdfs-vs-cloud-storage-pros-cons-and-migration-tips">several benefits</a> over HDFS: </p><ul><li><p>Significant cost reduction as compared to a long-running HDFS cluster with three replicas on persistent disks;</p></li><li><p>Separation of storage from compute, allowing you to grow each layer independently;</p></li><li><p>Persisting the storage even after Hadoop clusters are terminated;</p></li><li><p>Sharing Cloud Storage buckets between ephemeral Hadoop clusters;</p></li><li><p>No storage administration overhead, like managing upgrades and high availability for HDFS.</p></li></ul><p>The Cloud Storage Connector’s source code is completely open source and is supported by <a href="https://cloud.google.com/">Google Cloud Platform</a> (GCP). The connector comes pre-configured in <a href="https://cloud.google.com/dataproc/">Cloud Dataproc</a>, GCP’s managed Hadoop and Spark offering. However, it is also easily installed and fully supported for use in other Hadoop distributions such as <a href="https://mapr.com/support/s/article/Connecting-Google-Storage-bucket-from-MapR-host?language=en_US">MapR</a>, <a href="https://cloud.google.com/blog/products/storage-data-transfer/how-to-connect-clouderas-cdh-to-cloud-storage">Cloudera</a>, and <a href="https://community.hortonworks.com/articles/211804/accessing-google-cloud-storage-via-hdp.html">Hortonworks</a>. This makes it easy to migrate on-prem HDFS data to the cloud or burst workloads to GCP. </p><p>The open source aspect of the Cloud Storage Connector allowed <a href="https://twitter.com/twittereng">Twitter’s engineering team</a> to closely collaborate with us on the design, implementation, and productionizing of the fadvise and cooperative locking features at petabyte scale. </p><p><b>Cloud Storage Connector architecture<br/></b>Here’s a look at what the Cloud Storage Connector architecture looks like:</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="Diagram 2.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/Diagram_2.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Cloud Storage Connector is an <a href="https://github.com/GoogleCloudPlatform/bigdata-interop/blob/master/LICENSE">open source Apache 2.0</a> implementation of an <a href="http://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-common/filesystem/introduction.html#Core_Expectations_of_a_Hadoop_Compatible_FileSystem">HCFS</a> interface for Cloud Storage. Architecturally, it is composed of four major components:</p><ul><li><p><a href="https://github.com/GoogleCloudPlatform/bigdata-interop/tree/v2.0.0/gcs">gcs</a>—implementation of the <a href="https://hadoop.apache.org/docs/current/api/org/apache/hadoop/fs/FileSystem.html">Hadoop Distributed File System</a> and input/output channels</p></li><li><p><a href="https://github.com/GoogleCloudPlatform/bigdata-interop/tree/v2.0.0/util-hadoop">util-hadoop</a>—common (authentication, authorization) Hadoop-related functionality shared with other Hadoop connectors</p></li><li><p><a href="https://github.com/GoogleCloudPlatform/bigdata-interop/tree/v2.0.0/gcsio">gcsio</a>—high-level abstraction of <a href="https://cloud.google.com/storage/docs/json_api/">Cloud Storage JSON API</a></p></li><li><p><a href="https://github.com/GoogleCloudPlatform/bigdata-interop/tree/v2.0.0/util">util</a>—utility functions (error handling, HTTP transport configuration, etc.) used by gcs and gcsio components</p></li></ul><p>In the following sections, we highlight a few of the major features in this new release of Cloud Storage Connector. For a full list of settings and how to use them, check out the newly published <a href="https://github.com/GoogleCloudPlatform/bigdata-interop/blob/v2.0.0/gcs/CONFIGURATION.md">Configuration Properties</a> and <a href="https://github.com/GoogleCloudPlatform/bigdata-interop/blob/v2.0.0/gcs/conf/gcs-core-default.xml">gcs-core-default.xml</a> settings pages.</p><p>Here are the key new features of the Cloud Storage Connector:</p><p><b>Improved performance for Parquet and ORC columnar formats<br/></b>As part of Twitter’s <a href="https://blog.twitter.com/engineering/en_us/topics/infrastructure/2019/the-start-of-a-journey-into-the-cloud.html">migration of Hadoop to Google Cloud</a>, in mid-2018 Twitter started testing big data SQL queries against columnar files in Cloud Storage at massive scale, against a 20+ PB dataset. Since the Cloud Storage Connector is open source, Twitter prototyped the use of range requests to read only the columns required by the query engine, which increased read efficiency. We incorporated that work into a more generalized fadvise feature. </p><p>In previous versions of the Cloud Storage Connector, reads were optimized for MapReduce-style workloads, where all data in a file was processed sequentially. However, modern columnar file formats such as Parquet or ORC are designed to support predicate pushdown, allowing the big data engine to intelligently read only the chunks of the file (columns) that are needed to process the query. The Cloud Storage Connector now fully supports predicate pushdown, and only reads the bytes requested by the compute layer. This is done by introducing a technique known as fadvise. </p><p>You may already be familiar with the <a href="http://man7.org/linux/man-pages/man2/posix_fadvise.2.html">fadvise feature in Linux</a>. Fadvise allows applications to provide a hint to the Linux kernel with the intended I/O access pattern, indicating how it intends to read a file, whether for sequential scans or random seeks. This lets the kernel choose appropriate read-ahead and caching techniques to increase throughput or reduce latency.</p><p>The new fadvise feature in Cloud Storage Connector implements a similar functionality and automatically detects (in default auto mode) whether the current big data application’s I/O access pattern is sequential or random.</p><p>In the default auto mode, fadvise starts by assuming a sequential read pattern, but then switches to random mode upon detection of a backward seek or long forward seek. These seeks are performed by the <a href="https://docs.oracle.com/javase/8/docs/api/java/nio/channels/SeekableByteChannel.html#position-long-"><code>position()</code></a> method call and can change the current channel position backward or forward. Any backward seek triggers the mode change to random; however, a forward seek needs to be greater than 8 MB (configurable via <code>fs.gs.inputstream.inplace.seek.limit</code>). The read pattern transition (from sequential to random) in fadvise’s auto mode is stateless and gets reset for each new file read session.</p><p>Fadvise can be configured via the <a href="https://github.com/GoogleCloudPlatform/bigdata-interop/blob/v2.0.0/gcs/conf/gcs-core-default.xml">gcs-core-default.xml</a> file with the <a href="https://github.com/GoogleCloudPlatform/bigdata-interop/blob/v2.0.0/gcs/CONFIGURATION.md#fadvise-feature-configuration"><code>fs.gs.inputstream.fadvise parameter</code></a>:</p><ul><li><p>AUTO (default), also called adaptive range reads—In this mode, the connector starts in SEQUENTIAL mode, but switches to RANDOM as soon as the first backward or forward read is detected that’s greater than <code>fs.gs.inputstream.inplace.seek.limit</code> bytes (8 MiB by default).</p></li><li><p>RANDOM—The connector will send bounded range requests to Cloud Storage; Cloud Storage read-ahead will be disabled.</p></li><li><p>SEQUENTIAL—The connector will send a single, unbounded streaming request to Cloud Storage to read an object from a specified position sequentially.</p></li></ul><p>In most use cases, the default setting of AUTO should be sufficient. It dynamically adjusts the mode for each file read. However, you can hard-set the mode.</p><p>Ideal use cases for fadvise in RANDOM mode include:</p><ul><li><p>SQL (Spark SQL, Presto, Hive, etc.) queries into columnar file formats (Parquet, ORC, etc.) in Cloud Storage</p></li><li><p>Random lookups by a database system (HBase, Cassandra, etc.) to storage files (HFile, SSTables) in Cloud Storage</p></li></ul><p>Ideal use cases for fadvise in SEQUENTIAL mode include:</p><ul><li><p>Traditional MapReduce jobs that scan entire files sequentially</p></li><li><p>DistCp file transfers</p></li></ul><p><b>Cooperative locking: Isolation for Cloud Storage directory modifications<br/></b>Another major addition to Cloud Storage Connector is cooperative locking, which isolates directory modification operations performed through the <a href="https://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/FileSystemShell.html">Hadoop file system shell</a> (hadoop fs command) and other HCFS API interfaces to Cloud Storage.</p><p>Although Cloud Storage is <a href="https://cloud.google.com/storage/docs/consistency">strongly consistent</a> at the object level, it does not natively support directory semantics. For example, what should happen if two users issue conflicting commands (delete vs. rename) to the same directory? In HDFS, such directory operations are atomic and consistent. So <a href="https://twitter.com/Joep">Joep Rottinghuis</a>, leading the <a href="https://twitter.com/twitterhadoop">@TwitterHadoop</a> team, worked with us to implement cooperative locking in Cloud Storage Connector. This feature prevents data inconsistencies during conflicting directory operations to Cloud Storage, facilitates recovery of any failed directory operations, and simplifies operational migration from HDFS to Cloud Storage.</p><p>With cooperative locking, concurrent directory modifications that could interfere with each other, like a user deleting a directory while another user is trying to rename it, are safeguarded. Cooperative locking also supports recovery of failed directory modifications (where a JVM might have crashed mid-operation), via the FSCK command, which can resume or roll back the incomplete operation.</p><p>With this cooperative locking feature, you can now perform isolated directory modification operations, using the <code>hadoop fs</code> commands as you normally would to move or delete a folder:</p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><p>To recover failed directory modification operations performed with enabled Cooperative Locking, use the included FSCK tool:</p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><p>This command will recover (roll back or roll forward) all failed directory modification operations, based on the operation log.</p><p>The cooperative locking feature is intended to be used by human operators when modifying Cloud Storage directories through the <code>hadoop fs</code> interface. Since the underlying Cloud Storage system does not support locking, this feature should be used cautiously for use cases beyond directory modifications. (such as when a MapReduce or Spark job modifies a directory).</p><p>Cooperative locking is disabled by default. To enable it, either set <code>fs.gs.cooperative.locking.enable</code> Hadoop property to true in core-site.xml:</p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><p>or specify it directly in your <code>hadoop fs</code> command:</p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><p><b>How cooperative locking works<br/></b>Here’s what a directory move with cooperative locking looks like:</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="Diagram 3B.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/Diagram_3B.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Cooperative Locking is implemented via atomic lock acquisition in the lock file (<code>_lock/all.lock</code>) using <a href="https://cloud.google.com/storage/docs/generations-preconditions#_Preconditions">Cloud Storage preconditions</a>. Before each directory modification operation, the Cloud Storage Connector atomically acquires a lock in this bucket-wide lock file.</p><p>Additional operational metadata is stored in <code>*.lock</code> and <code>*.log</code> files in the <code>_lock</code> directory at the root of the Cloud Storage bucket. Operational files (a list of files to modify) are stored in a per-operation <code>*.log</code> file and additional lock metadata in per-operation <code>*.lock</code> file. This per-operation lock file is used for lock renewal and checkpointing operation progress.</p><p>The acquired lock will automatically expire if it is not periodically renewed by the client. The timeout interval can be modified via the <a href="https://github.com/GoogleCloudPlatform/bigdata-interop/blob/v2.0.0/gcs/CONFIGURATION.md#cooperative-locking-feature-configuration"><code>fs.gs.cooperative.locking.expiration.timeout.ms</code></a> setting.</p><p>Cooperative locking supports isolation of directory modification operations only in the same Cloud Storage bucket, and does not support directory moves across buckets.</p><p><b>Note</b>: Cooperative locking is a Cloud Storage Connector feature, and it is not implemented by gsutil, Object Lifecycle Management or applications directly using the Cloud Storage API.</p><p><b>General performance improvements to Cloud Storage Connector<br/></b>In addition to the above features, there are many other performance improvements and optimizations in this Cloud Storage Connector release. For example:</p><ul><li><p><b>Directory modification parallelization</b>, in addition to using batch request, the Cloud Storage Connector executes Cloud Storage batches in parallel, reducing the rename time for a directory with 32,000 files from 15 minutes to 1 minute, 30 seconds.</p></li><li><p><b>Latency optimizations</b> by decreasing the necessary Cloud Storage requests for high-level Hadoop file system operations.</p></li><li><p><b>Concurrent glob algorithms</b> (regular and flat glob) execution to yield the best performance for all use cases (deep and broad file trees).</p></li><li><p><b>Repair <a href="https://github.com/GoogleCloudPlatform/bigdata-interop/blob/v2.0.0/gcs/CONFIGURATION.md#general-configuration">implicit directories</a> during delete and rename operations</b> instead of list and glob operations, reducing latency of expensive list and glob operations, and eliminating the need for write permissions for read requests.</p></li><li><p><b>Cloud Storage <a href="https://github.com/GoogleCloudPlatform/bigdata-interop/blob/v2.0.0/gcs/CONFIGURATION.md#io-configuration">read consistency</a></b>to allow requests of the same Cloud Storage object version, preventing reading of different object versions and improving performance.</p></li></ul><p>You can upgrade to the new version of Cloud Storage Connector using the <a href="https://github.com/GoogleCloudPlatform/dataproc-initialization-actions/tree/master/connectors">connectors initialization action</a> for existing Cloud Dataproc versions. It will become standard starting in Cloud Dataproc version 2.0.</p><p><i>Thanks to contributors to the design and development of the new release of Cloud Storage Connector, in no particular order: Joep Rottinghuis, Lohit Vijayarenu, Hao Luo and Yaliang Wang from the Twitter engineering team.</i></p></div></div></body></html>Fri, 30 Aug 2019 13:00:00 -0000https://cloud.google.com/blog/products/data-analytics/new-release-of-cloud-storage-connector-for-hadoop-improving-performance-throughput-and-more/Storage & Data TransferGoogle Cloud PlatformData AnalyticsarticleNew release of Cloud Storage Connector for Hadoop: Improving performance, throughput and moreThe latest release of the Google Cloud Storage Connector for Hadoop makes it even easier to substitute your HDFS with Cloud Storage for high performance.https://storage.googleapis.com/gweb-cloudblog-publish/images/containers.max-600x600.pngGooglehttps://cloud.google.com/blog/products/data-analytics/new-release-of-cloud-storage-connector-for-hadoop-improving-performance-throughput-and-more/Igor DvorzhakSoftware EngineerSameer FarooquiCloud Data EngineerNow in beta: Managed Service for Microsoft Active Directory (AD)https://cloud.google.com/blog/products/identity-security/now-in-beta-managed-service-for-microsoft-active-directory-ad/<html><head></head><body><div class="block-paragraph"><div class="rich-text"><p>In April at Google Cloud Next ’19, we <a href="https://cloud.google.com/blog/products/identity-security/simplifying-identity-and-access-management-of-your-employees-partners-and-customers">announced</a> Managed Service for Microsoft Active Directory (AD) to help you manage AD-dependent workloads that run in the cloud, automate AD server maintenance and security configuration, and connect your on-premises AD domain to the cloud. Managed Service for Microsoft AD is now available in public beta. </p><h2>Simplifying Active Directory management</h2><p>As more AD-dependent apps and servers move to the cloud, IT and security teams face heightened challenges to meet latency and security goals, on top of the typical maintenance challenges of configuring and securing AD Domain Controllers. While you can deploy a fault-tolerant AD environment in GCP <a href="https://cloud.google.com/solutions/deploy-fault-tolerant-active-directory-environment">on your own</a>, we believe there’s an easier way that gives you time to focus on more impactful projects.</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--medium h-c-grid__col h-c-grid__col--4 h-c-grid__col--offset-4 "><img alt="GCP Active Directory management.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/GCP_Active_Directory_management.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p><a href="https://cloud.google.com/managed-microsoft-ad/">Managed Service for Microsoft AD</a> is a highly available, hardened Google Cloud service that delivers the following benefits:</p><ul><li><p>Actual Microsoft AD. The service runs real Microsoft AD Domain Controllers, so you don’t have to worry about application compatibility. You can use standard Active Directory features such as Group Policy, and familiar administration tools such as Remote Server Administration Tools (RSAT), to manage the domain. </p></li><li><p>Virtually maintenance-free. The service is highly available, automatically patched, configured with secure defaults, and protected by appropriate network firewall rules.</p></li><li><p>Seamless multi-region deployment. You can deploy the service in a specific region to allow your apps and VMs in the same or other regions access the domain over a low-latency <a href="https://cloud.google.com/vpc/">Virtual Private Cloud (VPC)</a>. As your infrastructure needs grow, you can simply expand the service to additional regions while continuing to use the same managed AD domain.</p></li></ul><p>Hybrid identity. You can <a href="https://cloud.google.com/solutions/patterns-for-using-active-directory-in-a-hybrid-environment">connect</a> your on-premises AD domain to Google Cloud or deploy a standalone domain for your cloud-based workloads.</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="Managed Service for Microsoft AD admin experience.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/Managed_Service_for_Microsoft_AD_admin_exp.max-1000x1000_mDS1sXa.png"/><figcaption class="article-image__caption "><div class="rich-text"><i>Managed Service for Microsoft AD admin experience</i></div></figcaption></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Customers and partners have already been using Managed Service for Microsoft AD for their AD-dependent applications and VMs. Use cases include automatically “domain joining” new Windows VMs by integrating the service with <a href="https://cloud.google.com/dns/">Cloud DNS</a>, hardening Windows VMs by applying Group Policy Objects (GPOs), and controlling Remote Desktop Protocol (RDP) access through GPOs. </p><p><a href="https://www.dunnhumby.com/">dunnhumby</a>, a customer data science platform, has been evaluating the service over the last few months. "We have been helping customers to better understand their customers for over 30 years," said Andrew Baird, Infrastructure Engineer, dunnhumby. "With Managed Service for Microsoft AD, we can now offload some of the AD management and security tasks, so we can focus on our main job—our customers."</p><p><a href="https://www.citrix.com/">Citrix</a> has also been evaluating the service to reduce the management overhead for their services that run on GCP. "Citrix Virtual Apps and Desktops service orchestrates customer workloads which run on a managed fleet of “VDA” instances on GCP. For the AD-related operations of these Citrix products, we found infrastructure deployment was significantly simplified with Google Cloud's managed services, especially Managed Service for Microsoft Active Directory," said Harsh Gupta, Director Product Management, Citrix.</p><h2>Getting started</h2><p>Managed Service for Microsoft AD is available in public beta. To get started, check out the <a href="https://cloud.google.com/managed-microsoft-ad/">product page</a> to sign up for beta, read the <a href="https://cloud.google.com/managed-microsoft-ad/docs/">documentation</a>, and watch the latest <a href="https://cloudonair.withgoogle.com/events/security-talks-august/watch?talk=microsoft-ad-mangement">webinar</a>.</p></div></div></body></html>Thu, 29 Aug 2019 16:00:00 -0000https://cloud.google.com/blog/products/identity-security/now-in-beta-managed-service-for-microsoft-active-directory-ad/Google Cloud PlatformCloud MigrationIdentity & SecurityarticleNow in beta: Managed Service for Microsoft Active Directory (AD)Managed Service for Microsoft Active Directory (AD) is now available in public beta.https://storage.googleapis.com/gweb-cloudblog-publish/images/GCP_Identity_Security.max-600x600.jpgGooglehttps://cloud.google.com/blog/products/identity-security/now-in-beta-managed-service-for-microsoft-active-directory-ad/Siddharth BhaiProduct Manager, Google CloudThe Speed Read with Quentin Hardy: Keep it simplehttps://cloud.google.com/blog/topics/speed-read/the-speed-read-with-quentin-hardy-keep-it-simple/<html><head></head><body><div class="block-paragraph"><div class="rich-text"><p><i><b>Editor’s note:</b>The Speed Read is a column authored by Google Cloud’s Quentin Hardy, examining important themes and hot topics in cloud computing. It previously existed as an email newsletter. Today, we’re thrilled to welcome it to its new home on the Cloud blog.</i></p><p>Some things in modern enterprise technology are a good deal harder to understand than they need to be. It is a great moment when we’re able to change that. </p><p>Take cloud services, for example. Microservices and service meshes are cloud technologies that will be important in your business life, and they are not all that strange. In fact, the mere concept of them should be familiar. They are really, really powerful as simplifiers that make innovation at scale possible. </p><p>Welcome to The Speed Read, “positive simplifier” edition. </p><p>As with many things in business, the secret to understanding these cloud computing technologies and techniques lies in establishing how their rise relates to supply and demand, the most fundamental elements of any market. With business technology, it’s also good to search for ways that an expensive and cumbersome process is being automated to hasten the delivery of value.</p><p>But what does this have to do with cloud services? At the first technology level, microservices are parts of a larger software application that can be decoupled from the whole and updated without having to break out and then redeploy the whole thing. Service meshes control how these parts interact, both with each other and other services. These complex tools exist with a single great business purpose in mind: to create reusable efficiency.</p><p>Think of each microservice as a tool from a toolbox. At one time, tools were custom made, and were used to custom make machines. For the most part, these machines were relatively simple, because they were single devices, no two alike, and that limited the building and the fixing of them. </p><p>Then with standardized measurement and industrial expansion, we got precision-made machine tools, capable of much more re-use and wider deployment. Those standardized machine tools were more complex than their predecessors. And they enabled a boom in standardized re-use, a simpler model overall.</p><p>The same goes with microservices—the piece parts are often more complex, but overall the process allows for standardized reuse through the management of service meshes. The “tool” in this case is software that carries out a function—doing online payments, say, or creating security verifications. </p><p>Extrapolating from this analogy, does the boom in microservices tell us that the computational equivalent of the Industrial Revolution is underway? Is this an indication of standardization that makes it vastly easier to create objects and experiences, revolutionizes cost models, and shifts industries and fortunes?</p><p>Without getting too grandiose about it, yeah.</p><p>You see it around you, in the creation of companies that come out of nowhere to invent and capture big markets, or in workforce transformations that allow work and product creation to be decoupled, much the way microservices are decouplings from larger applications. Since change is easier, you see it in the importance of data to determine how things are consumed, and in rapidly reconfiguring how things are made and what is offered. </p><p>Perhaps most important for readers like you is that you see it in the way businesses are re-evaluating how they apportion and manage work. Nothing weird about that; we do it all the time.</p><p>It is understandable how the complexity of tech generates anxiety among many of its most promising consumers. Typically a feature of business computing evolves from scarce and difficult knowledge. Its strength and utility makes it powerful, often faster than software developers can socialize it, or the general public can learn. Not that long ago, spreadsheets and email were weird too, for these reasons. </p><p>To move ahead, though, it’s important to recognize big, meaningful changes, and abstract their meaning into something logical and familiar. At a granular level, microservices may be complex, but their function is very straightforward: standardize in order to clear space for innovation.</p></div></div></body></html>Thu, 29 Aug 2019 13:00:00 -0000https://cloud.google.com/blog/topics/speed-read/the-speed-read-with-quentin-hardy-keep-it-simple/Google Cloud PlatformInside Google CloudThe Speed ReadarticleThe Speed Read with Quentin Hardy: Keep it simpleQuentin describes how microservices and service meshes are powerful simplifiers that make innovation at scale possible.https://storage.googleapis.com/gweb-cloudblog-publish/images/SpeedRead_Aug.max-600x600.jpgGooglehttps://cloud.google.com/blog/topics/speed-read/the-speed-read-with-quentin-hardy-keep-it-simple/Quentin HardyHead of Editorial, Google CloudHow Worldline puts APIs at the heart of payments serviceshttps://cloud.google.com/blog/products/api-management/how-worldline-puts-apis-at-the-heart-of-payments-services/<html><head></head><body><div class="block-paragraph"><div class="rich-text"><p><i><b>Editor’s note:</b>Today we hear from <a href="https://worldline.com/">Worldline</a>, a financial services organization that creates and operates digital platforms handling billions of critical transactions between companies, partners, and customers every year. In this post, Wordline head of alliances and partnerships Michaël Petiot and head of API platform support Tanja Foing explain how APIs and API management enable this €2.3 billion enterprise to offer its services to partners in a wide variety of industries.</i></p><p><a href="https://worldline.com/">Worldline</a> is the European leader in the payment and transactional services industry, with activities organized around three axes: merchant services, financial services including <a href="https://equensworldline.com/">equensWorldline</a>, and mobility and e-transactional services. In order to be more agile, we’re undergoing a transformation in how we work internally and with our partners, putting APIs at the heart of how we’re connecting with everyone.</p><p><b>Leveraging APIs for third-party collaboration<br/></b>Like most companies, Worldline collaborates more and more with third parties to deliver the products and services our customers expect. We want to move faster, and open up our platforms to partners who can develop new use cases in payments and customer engagement. To meet evolving technology, business, and regulatory demands for connecting our ecosystem of partners and developers, we needed a robust API platform. It was especially important to us that third parties could connect easily and securely to our platform. </p><p>We chose Google Cloud’s <a href="https://cloud.google.com/apigee/">Apigee</a> API management platform as our company-wide standard. Initially, we leaned toward an open source tool, but Apigee won us over, thanks to its complete feature set, available right out of the box. The Apigee security and analytics features are particularly important to us because of our collaboration with banking and fintech customers and partners. </p><p><b>Developing bespoke customer solutions<br/></b>Our first three API use cases include: digital banking, connected cars, and an internal developer platform. </p><p>Banks need their data to be properly categorized and highly secure, and Apigee gives us the tools to provide the right environment for them. Leveraging Apigee, our <a href="https://worldline.com/en/home/solutions/financial-services-equensworldline/m-digital-banking-platform.html">digital banking solution</a> offers a dedicated developer portal for our customers in a separate environment. It has its own architecture to access back-end services as well. With functionality ranging from trusted authentication to contract completion, payments, and contact management, Worldline digital banking customers can tap into APIs to interact with us at every stage. </p><p>An important trend in transport and logistics is the integration of real-time data with third parties. Our <a href="https://worldline.com/en/home/solutions/mobility-and-e-transactional-services/connected-living-solutions/connected-car.html">Connected Car</a> offering is a white-label solution that provides APIs for a car manufacturer’s fleet of cars. This offering enables fleet owners to exchange data with their entire ecosystem. It also offers a relatively closed environment with a limited number of developers accessing it, and we expose these APIs via the Apigee gateway. We use Apigee analytics features to track how the APIs are used and how they’re performing, and then make changes as needed. </p><p>Our third use case is internal; we’re building a developer portal in order to make APIs easier to access and quicker to deploy.</p><p>Our partner ecosystem includes lessors, insurance companies, repair shops, logistics companies and end-users. Everyone benefits from advanced APIs for real-time secure exchanges, combined with open-exchange protocols such as the Remote Fleet Management Systems standard (used by truck manufacturers) in order to provide the best service to customers.</p><p>We recently presented to the Worldline product management community how we can scale up to a large portfolio of API solutions using Apigee as an accelerator. the presentation was a success, and illustrates how we can leverage the platform as a tool for driving innovation throughout Worldline—and throughout our growing ecosystem of automotive and financial services customers</p></div></div></body></html>Thu, 29 Aug 2019 13:00:00 -0000https://cloud.google.com/blog/products/api-management/how-worldline-puts-apis-at-the-heart-of-payments-services/Google Cloud PlatformApigeeFinancial ServicesCustomersAPI ManagementarticleHow Worldline puts APIs at the heart of payments servicesFinancial services organization Worldline shares how API management enable it to offer its services to partners in a wide variety of industries.https://storage.googleapis.com/gweb-cloudblog-publish/images/GCP_Financial_Services.max-600x600.jpgGooglehttps://cloud.google.com/blog/products/api-management/how-worldline-puts-apis-at-the-heart-of-payments-services/Michael PetiotWorldlineTanja FoingWorldlineUsing Google Cloud Speech-to-Text to transcribe your Twilio calls in real-timehttps://cloud.google.com/blog/topics/partners/using-google-cloud-speech-to-text-to-transcribe-your-twilio-calls-in-real-time/<html><head></head><body><div class="block-paragraph"><div class="rich-text"><p>Developers have asked us how they can use Google Cloud’s Speech-to-Text to transcribe speech (especially phone audio) coming from <a href="https://www.twilio.com/">Twilio</a>, a leading cloud communications PaaS. We’re pleased to announce that it’s now easier than ever to integrate live call data with Google Cloud’s Speech-to-Text using Twilio’s Media Streams.</p><p>The new TwiML <i>&lt;stream&gt;</i> command streams call audio to a websocket server. This makes it simple to move your call audio from your business phone system into an AI platform that can transcribe that data in real time and use it for use cases like helping contact center agents and admins, as well as store it for later analysis. </p><p>When you combine this new functionality with Google Cloud’s Speech-to-Text abilities and other infrastructure and analytics tools like BigQuery, you can create an extremely scalable, reliable and accurate way of getting more value from your audio.</p><h2>Architecture</h2><p>The overall architecture for creating this flow looks something like what you see below. Twilio creates and manages the inbound phone number. Their new Stream command takes the audio from an incoming phone call and sends it to a configured websocket which runs on a simple App Engine flexible environment. From there, sending the audio along as it comes to Cloud Speech-to-Text is not very challenging. Once a transcript is created, it’s stored in BigQuery where real-time analysis can be performed.</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="twilio overall architecture.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/twilio_overall_architecture.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><h2>Configuring your phone number</h2><p>Once you’ve <a href="https://www.twilio.com/login?g=/console/phone-numbers/search?&amp;t=a3134facff1edad5ee8c40d35c3a85606f3a8f8a2dfeb64c6f4aedcf3f06da20">bought a number</a> in Twilio, you’ll need to configure your phone number to respond with <a href="https://www.twilio.com/docs/voice/twiml">TwiML</a>, which stands for Twilio Markup Language. It’s a tag-based language much like HTML, which will pass off control via a webhook that expects TwiML that you provide.</p><p>Next, navigate to your list <a href="https://www.twilio.com/console/phone-numbers/incoming">phone numbers</a> and choose your new number. On the number settings screen, scroll down to the <b>Voice</b> section. There is a field labelled “A Call Comes In”. Here, choose <b>TwiML Bin</b> from the drop down and press the plus button next to the field to create a new TwiML Bin.</p><h2>Creating a TwiML Bin</h2><p><a href="https://www.twilio.com/docs/runtime/tutorials/twiml-bins">TwiML Bins</a> are a serverless solution that can seamlessly host TwiML instructions. Using a TwiML Bin prevents you from needing to set up a webhook handler in your own web-hosted environment.</p><p>Give your TwiML Bin a Friendly Name that you can remember later. In the <b>Body</b> field, enter the following code, replacing the url attribute of the &lt;Stream&gt; tag and the phone number contained in the body of the &lt;Dial&gt; tag.</p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><p>The <a href="https://www.twilio.com/docs/voice/twiml/stream">&lt;Stream&gt;</a> tag starts the audio stream asynchronously and then control moves onto the &lt;Dial&gt; verb. &lt;Dial&gt; will call that number. The audio stream will end when the call is completed.</p><p>Save your TwiML Bin and make sure that you see your Friendly Name in the “A Call Comes In“ drop down next to TwiML Bin. Make sure to <b>Save</b> your phone number.</p><h2>Setup in Google Cloud</h2><p>This setup can either be done in an existing Google Cloud project or a new project. To set up a new project, follow the instructions <a href="https://cloud.google.com/resource-manager/docs/creating-managing-projects">here</a>. Once you have the project selected that you want to work in, you’ll need to set up a few key things before getting started:</p><ul><li><p>Enable APIs for Google Speech-to-Text. You can do that by following the instructions <a href="https://cloud.google.com/endpoints/docs/openapi/enable-api">here</a> and searching for “Cloud Speech-to-Text API”.</p></li><li><p><a href="https://cloud.google.com/iam/docs/creating-managing-service-accounts">Create</a> a service account for your App Engine flexible environment to utilize when accessing other Google Cloud services. You’ll need to download the private key as a JSON file as well.</p></li><li><p>Add firewall rules to allow your App Engine flexible environment to accept incoming connections for the websocket. A command like the following should work from a gcloud enabled terminal:</p></li><ul><li><p>gcloud compute firewall-rules create default-allow-websockets-8080 --allow tcp:8080 --target-tags websocket --description "Allow websocket traffic on port 8080"</p></li></ul></ul><h2>App Engine flexible environment setup</h2><p>For the App Engine application, we will be taking the sample code from Twilio’s repository to create a simple node.js websocket server. You can find the github page <a href="https://github.com/twilio/programmable-media-streams/tree/master/node/realtime-transcriptions">here</a> with instructions on environment setup. Once the code is in your project folder, you’ll need to do a few more things to deploy your application:</p><ul><li><p>Place the service account JSON key you downloaded earlier, rename it to “google_creds.json”, and put it in the same directory as the node.js code.</p></li><li><p>Create an app.yaml file that looks like the following:</p></li><ul><li><p>runtime: nodejs</p></li><li><p>env: flex</p></li><li><p>manual_scaling:</p></li><li><p>  instances: 1</p></li><li><p>network:</p></li><li><p>  instance_tag: websocket</p></li></ul></ul></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="App Engine flexible environment setup.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/App_Engine_flexible_environment_setup.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Once these two items are in order, you will be able to deploy your application with the command:</p><p><b><i>gcloud app deploy</i></b></p><p>Once deployed, you can tail the console logs with the command:</p><p><b><i>gcloud app logs tail -s default</i></b></p><h2>Verifying your stream is working</h2><p>Call your Twilio number, and you should immediately be connected with the number specified in your TwiML. You should see a websocket connection request made to the url specified in the &lt;Stream&gt;. Your websocket should immediately start receiving messages. If you are tailing the logs in the console, the application will log the intermediate messages as well as any final utterances detected by Google Cloud’s Speech-to-Text API.</p><h2>Writing transcriptions to BigQuery</h2><p>In order to analyze the transcripts later, we can create a BigQuery table and modify the sample code from Twilio to write to that table. Instructions for creating a new BigQuery table can be found <a href="https://cloud.google.com/bigquery/docs/tables">here</a>. Given the way Google Speech-to-Text creates transcription results, a potential schema for the table might look like the following.</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="Writing transcriptions to BigQuery.jpg" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/Writing_transcriptions_to_BigQuery.max-1000x1000.jpg"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Once a table like this exists, you can modify the Twilio sample code to also stream data to the BigQuery table using sample code found <a href="https://github.com/googleapis/nodejs-bigquery/blob/master/samples/insertRowsAsStream.js">here</a>.</p><h2>Conclusion</h2><p>Twilio’s new <i>Stream</i> function allows users to quickly make use of the real time audio that is moving through their phone systems. Paired with Google Cloud, that data can be transcribed in real time and passed on to numerous other applications. This ability to get high quality transcription in real time can benefit businesses—from helping contact center agents document and understand phone calls, to analyzing data from the transcripts of those calls. </p><p>To learn more about Cloud Speech-to-Text, <a href="https://cloud.google.com/speech-to-text/">visit our website</a>.</p></div></div></body></html>Wed, 28 Aug 2019 14:00:00 -0000https://cloud.google.com/blog/topics/partners/using-google-cloud-speech-to-text-to-transcribe-your-twilio-calls-in-real-time/AI & Machine LearningGoogle Cloud PlatformPartnersarticleUsing Google Cloud Speech-to-Text to transcribe your Twilio calls in real-timeIt’s now easier than ever to integrate live call data with Google Cloud’s Speech-to-Text using Twilio’s Media Streams.Googlehttps://cloud.google.com/blog/topics/partners/using-google-cloud-speech-to-text-to-transcribe-your-twilio-calls-in-real-time/Mark ShaldaTechnical Program Manager & ML Partner Engineering LeadSpot slow MySQL queries fast with Stackdriver Monitoringhttps://cloud.google.com/blog/products/management-tools/spot-slow-mysql-queries-fast-with-stackdriver-monitoring/<html><head></head><body><div class="block-paragraph"><div class="rich-text"><p>When you’re serving customers online, speed is essential for a good experience. As the amount of data in a database grows, queries that used to be fast can slow down. For example, if a query has to scan every row because a table is missing an index, response times that were acceptable with a thousand rows can turn into multiple seconds of waiting once you have a million rows. If this query is executed every time a user loads your web page, their browsing experience will slow to a crawl, causing user frustration. Slow queries can also impact automated jobs, causing them to time out before completion. If there are too many of these slow queries executing at once, the database can even run out of connections, causing all new queries, slow or fast, to fail. </p><p>The popular open-source databases MySQL and <a href="https://cloud.google.com/">Google Cloud Platform</a>'s fully managed version, <a href="http://cloud.google.com/sql">Cloud SQL for MySQL</a>, include a feature to log slow queries, letting you find the cause, then optimize for better performance. However, developers and database administrators typically only access this slow query log reactively, after users have seen the effects and escalated the performance degradation.</p>With <a href="https://cloud.google.com/logging/">Stackdriver Logging</a> and <a href="https://cloud.google.com/monitoring/">Monitoring</a>, you can stay ahead of the curve for database performance with automatic alerts when query latency goes over the threshold, and a monitoring dashboard that lets you quickly pinpoint the specific queries causing the slowdown.</div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="Architecture for monitoring MySQ.jpg" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/Architecture_for_monitoring_MySQ.max-1000x1000.jpg"/><figcaption class="article-image__caption "><div class="rich-text"><i>Architecture for monitoring MySQL slow query logs with Stackdriver</i></div></figcaption></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>To get started, import MySQL's slow query log into Stackdriver Logging. Once the logs are in Stackdriver, it's straightforward to set up <a href="https://cloud.google.com/logging/docs/logs-based-metrics/">logs-based metrics</a> that can both count the number of slow queries over time, which is useful for setting up appropriate alerts, and also provide breakdowns by slow SQL statement, allowing speedy troubleshooting. What's more, this approach works equally well for managed databases in Cloud SQL for MySQL and for self-managed MySQL databases hosted on Compute Engine. </p>For a step-by-step tutorial to set up slow query monitoring, check out <a href="https://cloud.google.com/community/tutorials/stackdriver-monitor-slow-query-mysql">Monitoring slow queries in MySQL with Stackdriver</a>. For more ideas about what else you can accomplish with Stackdriver Logging, check out <a href="https://cloud.google.com/solutions/design-patterns-for-exporting-stackdriver-logging">Design patterns for exporting Stackdriver Logging</a>.</div></div></body></html>Wed, 28 Aug 2019 14:00:00 -0000https://cloud.google.com/blog/products/management-tools/spot-slow-mysql-queries-fast-with-stackdriver-monitoring/Google Cloud PlatformManagement ToolsarticleSpot slow MySQL queries fast with Stackdriver MonitoringUse Stackdriver Monitoring and Logging to quickly see why your MySQL or CloudSQL for MySQL queries are running slowly.https://storage.googleapis.com/gweb-cloudblog-publish/images/Google_Cloud_Management-Tools.max-600x600.jpgGooglehttps://cloud.google.com/blog/products/management-tools/spot-slow-mysql-queries-fast-with-stackdriver-monitoring/Jani PatokallioSolutions ArchitectJungwoon LeeCustomer EngineerWhat’s happening in BigQuery: Adding speed and flexibility with 10x streaming quota, Cloud SQL federation and morehttps://cloud.google.com/blog/products/data-analytics/whats-happening-bigquery-adding-speed-and-flexibility-10x-streaming-quota-cloud-sql-federation-and-more/<html><head></head><body><div class="block-paragraph"><div class="rich-text"><p>We’ve been busy this summer releasing new features for BigQuery, Google Cloud’s petabyte-scale data warehouse. BigQuery lets you ingest and analyze data quickly and with high availability, so you can find new insights, trends, and predictions to efficiently run your business. Our Google Cloud engineering team is continually making improvements to BigQuery to accelerate your time to value. </p><p>Recently added BigQuery features include a newly built back end with 10x the streaming quota, the ability to query live from Cloud SQL datasets, and the ability to run your existing TensorFlow models in BigQuery. These new features are designed to help you stream, analyze, and model more data faster, with more flexibility.</p><p>Read on to learn more about these new capabilities and get quick demos and tutorial links so you can try these features yourself.</p><h2>10x BigQuery streaming quota, now in beta</h2><p>We know your data needs to move faster than your business, so we’re always working on adding efficiency and speed. The BigQuery team has completely redesigned the streaming back end to increase the <a href="https://cloud.google.com/bigquery/quotas#streaming_inserts">default Streaming API quota</a> by a factor of 10, from 100,000 to 1,000,000 rows per second per project. The default quota for maximum bytes per second has also increased, from 100MB per table to 1GB per project and there are now no table-level limitations. This means you get greater capacity and better performance for your streaming workloads like IoT and more. </p><p>There’s no change to the current streaming API. You can choose whether you’d like to use this new streaming back end by filling out this <a href="https://docs.google.com/forms/d/1BpoUfWkHXxgl2m41PnSuufgiN2qvyhBfqDZRRZ9EX5E/">form</a>. If you use the new back end, you won’t have to change your BigQuery API code, since the new back end uses the same <a href="https://cloud.google.com/bigquery/streaming-data-into-bigquery">BigQuery Streaming API</a>. </p><p>Note that this quota increase is only applicable if you don’t need the <a href="https://cloud.google.com/bigquery/streaming-data-into-bigquery#dataconsistency">best effort deduplication</a> that’s offered by the current streaming back end. This is done by not populating the insertId field for each row inserted when calling the streaming API.</p><p>Check out this demo from Google Cloud Next ‘19 to see data stream 20 GB per second from simulated IoT sensors into BigQuery.</p></div></div><div class="block-video"><div class="article-module article-video "><figure><a class="h-c-video h-c-video--marquee" data-glue-modal-disabled-on-mobile="true" data-glue-modal-trigger="uni-modal-eOQ3YJKgvHE-" href="https://youtube.com/watch?v=eOQ3YJKgvHE"><img alt="BigQuery co-founder, Jordan Tigani, describes how today’s enterprise demands from data go far beyond the capabilities of traditional data warehousing. Leaders want to make real-time decisions from fresh information even while that data is growing rapidly. Companies can no longer analyze only what happened yesterday, they need to be able to make future predictions. Cruise Automation will share how they are using BigQuery to get answers to problems that could not be solved in traditional data warehouses. Jordan will also demonstrate some of the latest BigQuery features that will make you rethink what a data warehouse can be and how it can help you focus on the analytics instead of worrying about the infrastructure." src="//img.youtube.com/vi/eOQ3YJKgvHE/maxresdefault.jpg"/><svg class="h-c-video__play h-c-icon h-c-icon--color-white" role="img"><use xlink:href="#mi-youtube-icon"></use></svg></a></figure></div><div class="h-c-modal--video" data-glue-modal="uni-modal-eOQ3YJKgvHE-" data-glue-modal-close-label="Close Dialog"><a class="glue-yt-video" data-glue-yt-video-autoplay="true" data-glue-yt-video-height="99%" data-glue-yt-video-vid="eOQ3YJKgvHE" data-glue-yt-video-width="100%" href="https://youtube.com/watch?v=eOQ3YJKgvHE" ng-cloak=""></a></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Check out the documentation for more on <a href="https://cloud.google.com/bigquery/streaming-data-into-bigquery">Streaming data into BigQuery</a>.</p><h2>Query Cloud SQL from BigQuery</h2><p>Data can only create value for your business when you put it to work, and businesses need secure and easy-to-use methods to explore and manage data that is stored in multiple locations. Within Google Cloud, we use our database tools and services to power what we do, including offering new <a href="https://google.qwiklabs.com/">Qwiklabs</a> and <a href="https://cloud.google.com/training/">courses</a> each month. Internally, we manage the roadmap of new releases with a <a href="https://cloud.google.com/sql/docs/">Cloud SQL</a> back end. We then have an hourly Cloud Composer job that pipes our Cloud SQL transactional data from Cloud SQL into BigQuery for reporting. Such periodic export carries considerable overhead and the drawback that reports reflect data that is an hour old. This is a common challenge for enterprise business intelligence teams who want quicker insights from their transactional systems. </p><p>To avoid the overhead of periodic exports and increase the timeliness of your reports, we have expanded support for <a href="https://cloud.google.com/bigquery/external-data-sources">federated queries</a> to include Cloud SQL. You can now query your Cloud SQL tables and views directly from BigQuery through a <a href="https://cloud.google.com/bigquery/docs/cloud-sql-federated-queries">federated Cloud SQL connection</a> (no more moving or copying data). Our curriculum dashboards now run on live data with one simple <a href="http://cloud.google.com/bigquery/docs/cloud-sql-federated-queries#federated_query_syntax">EXTERNAL_QUERY()</a> instead of a complex hourly pipeline. This new connection feature supports both MySQL (second generation) and PostgreSQL instances in Cloud SQL. </p><p>After the initial one-time setup, you can write a query with the new SQL function <a href="http://cloud.google.com/bigquery/docs/cloud-sql-federated-queries#federated_query_syntax">EXTERNAL_QUERY()</a>. Here’s an example where we join existing customer data from BigQuery against the latest orders from our transactional system in Cloud SQL in one query:<br/></p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Note the cross database JOIN on rq.customer_id = c.customer_id. BigQuery actively connects to Cloud SQL to get the latest order data. </p><p>Getting live data from Cloud SQL federated in BigQuery means you will always have the latest data for reporting. This can save teams time, bring the latest data faster, and open up analytics possibilities. We hear from customers that they are seeing the benefits of immediate querying, too.</p><p>"Our data is spread across Cloud SQL and BigQuery. We had to maintain and monitor extract jobs to copy Cloud SQL data into BigQuery for analysis, and data was only as fresh as the last run,” says Zahi Karam, director of data science at Bluecore. “With Cloud SQL Federation, we can use BigQuery to run analysis across live data in both systems, ensuring that we're always getting the freshest view of our data. Additionally, we can securely enable less technical analysts to query Cloud SQL via BigQuery without having to set up additional connections."</p><p>Take a look at the demo for more:<br/></p></div></div><div class="block-video"><div class="article-module article-video "><figure><a class="h-c-video h-c-video--marquee" data-glue-modal-disabled-on-mobile="true" data-glue-modal-trigger="uni-modal-K8A6_G3DTTs-" href="https://youtube.com/watch?v=K8A6_G3DTTs"><img alt="This demo shows how to run a federated query from BigQuery against Cloud SQL. This feature uses the new EXTERNAL_QUERY function to pass a SQL query to the underlying MySQL or Postgres database in Cloud SQL." src="//img.youtube.com/vi/K8A6_G3DTTs/maxresdefault.jpg"/><svg class="h-c-video__play h-c-icon h-c-icon--color-white" role="img"><use xlink:href="#mi-youtube-icon"></use></svg></a></figure></div><div class="h-c-modal--video" data-glue-modal="uni-modal-K8A6_G3DTTs-" data-glue-modal-close-label="Close Dialog"><a class="glue-yt-video" data-glue-yt-video-autoplay="true" data-glue-yt-video-height="99%" data-glue-yt-video-vid="K8A6_G3DTTs" data-glue-yt-video-width="100%" href="https://youtube.com/watch?v=K8A6_G3DTTs" ng-cloak=""></a></div></div><div class="block-paragraph"><div class="rich-text"><p>Check out the documentation to learn more about <a href="https://cloud.google.com/bigquery/docs/cloud-sql-federated-queries">Cloud SQL federated queries from BigQuery</a>.</p><h2>BigQuery ML: Import TensorFlow models </h2><p>Machine learning can do lots of cool things for your business, but it needs to be easy and fast for users. For example, say your data science teams have created a couple of models and they need your help to make quick batch predictions on new data arriving in BigQuery. With new BigQuery ML <a href="https://cloud.google.com/bigquery-ml/docs/making-predictions-with-imported-tensorflow-models">Tensorflow prediction support</a>, you can import and make batch predictions using your existing TensorFlow models on your BigQuery tables, using familiar BQML syntax. Here’s an example.</p><p>First, we’ll import the model from our project bucket:</p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Then we can quickly batch predictions with the familiar BigQuery ML syntax:</p></div></div><div class="block-code"><div class="article-module h-c-page"><div class="h-c-grid uni-paragraph-wrap"><div class="uni-paragraph h-c-grid__col h-c-grid__col--8 h-c-grid__col-m--6 h-c-grid__col-l--6 h-c-grid__col--offset-2 h-c-grid__col-m--offset-3 h-c-grid__col-l--offset-3"><pre><code></code></pre></div></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Want to run batch predictions at regular intervals as new data comes in? Simply set up a <a href="https://cloud.google.com/bigquery/docs/scheduling-queries">scheduled query</a> to pull the latest data and also make the prediction. And as we highlighted in a previous post, scheduled queries can run as frequently as every <a href="https://cloud.google.com/blog/products/data-analytics/new-persistent-user-defined-functions-increased-concurrency-limits-gis-and-encryption-functions-and-more">15 minutes</a>.</p><p>Check out the <a href="https://cloud.google.com/bigquery-ml/docs/making-predictions-with-imported-tensorflow-models">BigQuery ML TensorFlow User Guide</a> for more.</p><h2>Automatic re-clustering now available </h2><p>Efficiency is essential when you’re crunching through huge datasets. One key best practice for cost and performance optimization in BigQuery is table <a href="https://cloud.google.com/bigquery/docs/partitioned-tables">partitioning</a> and <a href="https://cloud.google.com/bigquery/docs/clustered-tables">clustering</a>. As new data is added to your partitioned tables, it may get written into an active partition and need to be periodically re-clustered for better performance. Traditionally, other data warehouse processes like “<a href="https://docs.aws.amazon.com/redshift/latest/dg/r_VACUUM_command.html">VACUUM</a>” and “<a href="https://docs.snowflake.net/manuals/user-guide/tables-auto-reclustering.html">automatic clustering</a>” require setup and financing by the user. BigQuery now <a href="https://cloud.google.com/bigquery/docs/clustered-tables#automatic_re-clustering">automatically re-clusters</a> your data for you at no additional cost and with no action needed on your part.</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="Automatic re-clustering now available.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/Automatic_re-clustering_now_available.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Check out our recent blog post <a href="https://cloud.google.com/blog/products/data-analytics/skip-the-maintenance-speed-up-queries-with-bigquerys-clustering">Skip the maintenance, speed up queries with BigQuery's clustering</a> for a detailed walkthrough. And get more detail in the documentation: <a href="https://cloud.google.com/bigquery/docs/clustered-tables#automatic_re-clustering">automatic re-clustering</a>.</p><h2>UDF performance now faster</h2><p>If you perform a query using <a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/user-defined-functions">JavaScript UDFs</a>, it’ll now take around a second less to execute, on average, due to speedier logic for initializing the JavaScript V8 Engine that BigQuery uses to compute UDFs. Don’t forget you can <a href="https://cloud.google.com/blog/products/data-analytics/new-persistent-user-defined-functions-increased-concurrency-limits-gis-and-encryption-functions-and-more">persist and share your custom UDFs</a> with your team, as we highlighted in our last post. </p><h2>In case you missed it</h2><p>For more on all things BigQuery, check out these recent posts, videos and how-tos:</p><ul><li><p><a href="https://cloud.google.com/blog/products/data-analytics/skip-the-heavy-lifting-moving-redshift-to-bigquery-easily">Skip the heavy lifting: Moving Redshift to BigQuery easily</a></p></li><li><p><a href="https://cloud.google.com/blog/products/data-analytics/introducing-the-bigquery-terraform-module">Introducing the BigQuery Terraform module</a></p></li><li><p><a href="https://towardsdatascience.com/clustering-4-000-stack-overflow-tags-with-bigquery-k-means-ef88f902574a">Clustering 4,000 Stack Overflow tags with BigQuery k-means</a></p></li><li><p><a href="https://medium.com/google-cloud/efficient-spatial-matching-in-bigquery-c4ddc6fb9f69">Efficient spatial matching in BigQuery</a></p></li><li><p>Lab series: <a href="https://www.qwiklabs.com/quests/55">BigQuery for data analysts </a></p></li><li><p><a href="https://cloud.google.com/blog/products/data-analytics/glidefinder-how-we-built-a-platform-on-google-cloud-that-can-monitor-wildfires">GlideFinder: How we built a platform on Google Cloud that can monitor wildfires</a></p></li><li><p><a href="https://cloud.google.com/blog/products/data-analytics/migrating-teradata-and-other-data-warehouses-to-bigquery">Migrating Teradata and other data warehouses to BigQuery</a></p></li><li><p><a href="https://cloud.google.com/blog/products/data-analytics/how-to-use-bigquery-ml-for-anomaly-detection">How to use BigQuery ML for anomaly detection</a></p></li><li><p><a href="https://github.com/GoogleCloudPlatform/bigquery-utils">BigQuery shared utilities GitHub library (scripts, UDFs)</a></p></li></ul><p>To keep up on what’s new with BigQuery, subscribe to our <a href="https://cloud.google.com/bigquery/docs/release-notes">release notes</a> and stay tuned to the blog for news and announcements And <a href="https://twitter.com/gcpcloud?lang=en">let us know</a> how else we can help.</p></div></div></body></html>Wed, 28 Aug 2019 14:00:00 -0000https://cloud.google.com/blog/products/data-analytics/whats-happening-bigquery-adding-speed-and-flexibility-10x-streaming-quota-cloud-sql-federation-and-more/Google Cloud PlatformBigQueryData AnalyticsarticleWhat’s happening in BigQuery: Adding speed and flexibility with 10x streaming quota, Cloud SQL federation and moreThe latest updates for Google Cloud’s BigQuery data warehouse include a streaming quota increase, automatic re-clustering, and lots more features.https://storage.googleapis.com/gweb-cloudblog-publish/images/Cloud_BigQuery.max-600x600.jpgGooglehttps://cloud.google.com/blog/products/data-analytics/whats-happening-bigquery-adding-speed-and-flexibility-10x-streaming-quota-cloud-sql-federation-and-more/Evan JonesTechnical Curriculum Developer, Google CloudMusic to their ears: microservices on GKE, Preemptible VMs improved Musiio’s efficiency by 7000%https://cloud.google.com/blog/products/containers-kubernetes/microservices-on-gke-preemptible-vms-improved-musiios-efficiency-by-7000/<html><head></head><body><div class="block-paragraph"><div class="rich-text"><p><i><b>Editor’s note:</b> Advanced AI startup Musiio, the first ever VC-funded music tech company in Singapore, needed more robust infrastructure for the data pipeline it uses to ingest and analyze new music. Moving to Google Kubernetes Engine gave them the reliability they needed; rearchitecting their application as a series of microservices running on Preemptible VMs gave them new levels of efficiency and helped to control their costs. Read on to hear how they did it.</i></p><p>At <a href="https://www.musiio.com/home">Musiio</a> we’ve built an AI that ‘listens’ to music tracks to recognize thousands of characteristics and features from them. This allows us to create highly accurate tags, allow users to search based on musical features, and automatically create personalized playlists. We do this by indexing, classifying and ultimately making searchable new music as it gets created—to the tune of about 40,000 tracks each day for one major streaming provider.</p><p>But for this technology to work at scale, we first need to efficiently scan tens of millions of digital audio files, which represent terabytes upon terabytes of data. </p><p>In Musiio’s early days, we built a container-based pipeline in the cloud orchestrated by Kubernetes, organized around a few relatively heavy services. This approach had multiple issues, including low throughput, poor reliability and high costs. Nor could we run our containers with a high node-CPU utilization for an extended period of time; the nodes would fail or time out and become unresponsive. That made it almost impossible to diagnose the problem or resume the task, so we’d have to restart the scans.</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="musiio initial platform architecture.jpg" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/musiio_initial_platform_architecture.max-1000x1000.jpg"/><figcaption class="article-image__caption "><div class="rich-text"><i>Figure 1: Our initial platform architecture.</i></div></figcaption></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>As a part of reengineering our architecture, we decided to experiment with <a href="https://cloud.google.com/kubernetes-engine/">Google Kubernetes Engine</a> (GKE) on <a href="https://cloud.google.com/">Google Cloud Platform</a> (GCP). We quickly discovered some important advantages that allowed us to improve performance and better manage our costs: </p><ul><li><b>GKE reliability</b>: We were very impressed by GKE’s reliability, as we were able to run the nodes at &gt;90% CPU load for hours without any issues. On our previous provider, the nodes could not take a high CPU load and would often become unreachable.</li><li><b>Preemptible VMs and GPUs</b>: GKE supports both <a href="https://cloud.google.com/preemptible-vms/">Preemptible VMs</a> and <a href="https://cloud.google.com/compute/docs/gpus/#preemptible_with_gpu">GPUs on preemptible instances</a>. Preemptible VMs only last up to 24 hours but in exchange are up to 80% cheaper than regular compute instances; attached GPUs are also discounted. They can be reclaimed by GCP at any time during these 24 hours (along with any attached GPUs). However, reclaimed VMs do not disappear without warning. GCP sends a signal 30 seconds in advance, so your code has time to react. </li></ul><p>We wanted to take advantage of GKE’s improved performance and reliability, plus lower costs with preemptible resources. To do so, though, we needed to implement some simple changes to our architecture. </p><h2>Building a microservices-based pipeline</h2><p>To start, we redesigned our architecture to use lightweight microservices, and to follow one of the most important principles of software engineering: keep it simple. Our goal was that no single step in our pipeline would take more than 15 seconds, and that we could automatically resume any job wherever it left off. To achieve this we mainly relied on three GCP services:</p><ol><li><p><a href="https://cloud.google.com/pubsub/docs/overview">Google Cloud Pub/Sub</a> to manage the task queue,</p></li><li><p><a href="https://cloud.google.com/storage/">Google Cloud Storage</a> to store the temporary intermediate results, taking advantage of its <a href="https://cloud.google.com/storage/docs/managing-lifecycles">object lifecycle management</a>to do automatic cleanup, and</p></li><li><p><a href="https://cloud.google.com/kubernetes-engine/">GKE</a> with preemptible nodes to run the code.</p></li></ol><p>Specifically, the new processing pipeline now consists of the following steps:</p><ol><li><p>New tasks are added through an exposed API-endpoint by the clients.</p></li><li><p>The task is published to Cloud Pub/Sub and attached data is passed to a cloud storage bucket.</p></li><li><p>The services pulls new tasks from the queue and reports success status.</p></li><li><p>The final output is stored in a database and all intermediate data is discarded.</p></li></ol></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="musiio new improved architecture.jpg" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/musiio_new_improved_architecture.max-1000x1000.jpg"/><figcaption class="article-image__caption "><div class="rich-text"><i>Figure 2: Our new improved architecture.</i></div></figcaption></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>While there are more components in our new architecture, they are all much less complex. Communication is done through a queue where each step of the pipeline reports its success status. Each sub-step takes less than 10 seconds and can easily and quickly resume from the previous state and with no data loss. </p><h2>How do Preemptible VMs fit in this picture?</h2><p>Using preemptible resources might seem like an odd choice for a mission-critical service, but because of our microservices design, we were able to use Preemptible VMs and GPUs without losing data or having to write elaborate retry code. Using Cloud Pub/Sub (see 2. above) allows us to store the state of the job in the queue itself. If a service is notified that a node has been preempted, it finishes the current task (which, by design, is always shorter than the 30-second notification time), and simply stops pulling new tasks. Individual services don't have to do anything else to manage potential interruptions. When the node is available again, services begin pulling tasks from the queue again, starting where they left off.</p><p>This new design means that preemptible nodes can be added, taken away, or exchanged for regular nodes without causing any noticeable interruption.</p><p>GKE’s <a href="https://cloud.google.com/kubernetes-engine/docs/concepts/cluster-autoscaler">Cluster Autoscaler</a> also works very well with preemptible instances. By combining the auto scaling features (which automatically replaces nodes that have been reclaimed) with node labels, we were able to achieve an architecture with &gt;99.9% availability that runs primarily on preemptible nodes. </p><h2>Finally... </h2><p>We did all this over the course of a month—one week for design, and three weeks for the implementation. Was it worth all this effort? Yes! </p><p>With these changes, we increased our throughput from 100,000 to 7 million tracks per week—and <b>at the same cost as before!</b> This is a <b>7000% increase</b> (!) in efficiency, and was a crucial step in making our business profitable. </p><p>Our goal as a company is to be able to transform the way the music industry handles data and volume and make it efficient. With nearly 15 million songs being added to the global pool each year, access and accessibility are the new trend. Thanks to our new microservices architecture and the speed and reliability of Google Cloud, we are on our way to make this a reality. </p><p>Learn more about GKE on the <a href="https://cloud.google.com/kubernetes-engine">Google Cloud Platform website.</a></p></div></div></body></html>Wed, 28 Aug 2019 14:00:00 -0000https://cloud.google.com/blog/products/containers-kubernetes/microservices-on-gke-preemptible-vms-improved-musiios-efficiency-by-7000/Google Cloud PlatformCustomersCloud NativeContainers & KubernetesarticleMusic to their ears: microservices on GKE, Preemptible VMs improved Musiio’s efficiency by 7000%By using GKE and preemptible VMs on Google Cloud, Musiio was able to dramatically improve the efficiency of its microservices-based environment.https://storage.googleapis.com/gweb-cloudblog-publish/images/Google_Containers.max-600x600.jpgGooglehttps://cloud.google.com/blog/products/containers-kubernetes/microservices-on-gke-preemptible-vms-improved-musiios-efficiency-by-7000/Aron PetterssonCTO, MusiioWith great compute power: Rendering ‘Spider-Man: Far From Home’ on Google Cloudhttps://cloud.google.com/blog/products/compute/luma-pictures-render-spider-man-far-from-home-on-google-cloud/<html><head></head><body><div class="block-paragraph"><div class="rich-text"><p>In <i>Spider-Man: Far From Home</i>, Spidey leaves the friendly confines of New York City and goes on a school trip to Venice, Prague, Berlin and London (but not Paris). While working on the visual effects (VFX) for the film, Luma Pictures also left the comfort of its on-premises Los Angeles data center, moving its render pipeline to Google Cloud, where the movie’s Air and Fire Elemental characters (a.k.a., Cyclone and Molten Man) were generated.</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="LumaPictures_SpiderMan_GCP_2.jpg" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/LumaPictures_SpiderMan_GCP_2.max-1000x1000.jpg"/><figcaption class="article-image__caption "><div class="rich-text"><i>Images provided by Luma Pictures.</i></div></figcaption></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>“This was remarkable,” said Michael Perdew, a VFX producer at Luma Pictures. Initially, Luma didn’t think the cloud would be a good fit for the latest Spider-Man. “The big technical challenge here was that both of these characters were simulations,” he said. Historically, simulations took too much CPU, bandwidth, and disk space to be rendered in a time- or cost-effective manner outside of a local compute farm. Syncing terabytes of cache data from on-premises to the cloud can take several hours if you have limited bandwidth. In addition, Luma hadn’t yet found a cloud-based file system that could support the massive compute clusters you need to render simulations.<br/></p><p>But this was a big job, and “we had to find a way to render more than our local farms could handle,” Perdew said. So they put their heads together and developed a workflow to make it work in the cloud. </p><p>As it turned out, the cloud turned out to be the perfect place for this project—specifically for Cyclone. In Google Cloud, Luma leveraged Compute Engine custom images with 96-cores and 128 GB of RAM, and paired them with a high-performance ZFS file system. Using up to 15,000 vCPUs, Luma could render shots of the cloud monster in as little as 90 minutes—compared with the 7 or 8 hours it would take on their local render farm. Time saved rendering in the cloud more than made up for time spent syncing data to Google Cloud. “We came out way ahead, actually,” Perdew said.</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="LumaPictures_SpiderMan_GCP_3.jpg" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/LumaPictures_SpiderMan_GCP_3.max-1000x1000.jpg"/><figcaption class="article-image__caption "><div class="rich-text"><i>Images provided by Luma Pictures.</i></div></figcaption></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Leveraging the cloud also pushed Luma to get savvy with their workflow. By breaking up the Cyclone simulations into pieces, they could work around the clock—and around the world—tapping into the speed of our global fiber network that moves data around the planet. When the L.A. team slept, VFX artists in Luma’s Melbourne, Australia office tweaked animations and simulation settings, and triggered syncs to the cloud, getting the updated scenes ready for the L.A.-based FX and lighting teams. When L.A. artists arrived in the office the next morning, they could start the simulation jobs in Google Cloud, receiving data to review by lunchtime. <br/></p><p>In the end, Luma completed about 330 shots for <i>Spider-Man: Far From Home</i>—with about a third created in the cloud. In addition to creating Cyclone and Molten Man, Luma designed Spider-Man’s Night Monkey suit, created an elaborate CG environment for the Liberec Square in the Molten Man Battle scene, and collaborated on destruction FX in Mysterio’s lair sequence.</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="LumaPictures_SpiderMan_GCP_0.jpg" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/LumaPictures_SpiderMan_GCP_0.max-1000x1000.jpg"/><figcaption class="article-image__caption "><div class="rich-text"><i>Images provided by Luma Pictures.</i></div></figcaption></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Now that Luma’s work on Spider-Man is done, the studio is ramping up to take advantage of other GCP features. For example, its artists use an in-house proprietary tool called Rill that automates the process of seeing updated character animations through full simulations and render. This tool is currently deployed on an on-prem Kubernetes cluster, which they are exploring migrating—as well as other tools—to Google Kubernetes Engine (GKE) in the cloud. “Having more day-to-day services in the cloud will have all kinds of reliability benefits,” Perdew said, for example, protecting them against the power outages that occasionally happen in Luma’s Santa Monica office.</p><p>Additionally, Luma will install a direct connection to the Google Cloud Los Angeles cloud region (which celebrated its one-year anniversary this summer) for future productions, more bandwidth, and reduced latency to Google Cloud. The team hopes this will open the door to all kinds of possibilities; for example, Perdew is excited to try out remote workstations. “The industry keeps on changing the type of computer you need per discipline to do good work,” he said. “Having the flexibility to upgrade and downgrade an individual artist on the fly…as a producer, that makes me giddy.” </p><p>Here at Google Cloud, we’re also giddy to have helped bring Spider Man’s latest adventure to the big screen. But with great (compute) power comes great responsibility—we’re working diligently to make Google Cloud a great place to render your upcoming production. To learn more about Google Cloud in the media and entertainment industry, swing on over to our <a href="https://cloud.google.com/solutions/media-entertainment/use-cases/rendering/">Rendering Solutions page</a>.</p></div></div></body></html>Wed, 28 Aug 2019 11:00:00 -0000https://cloud.google.com/blog/products/compute/luma-pictures-render-spider-man-far-from-home-on-google-cloud/CustomersMedia & EntertainmentGoogle Cloud PlatformComputearticleWith great compute power: Rendering ‘Spider-Man: Far From Home’ on Google CloudLuma Pictures relied on high-performance compute from Google Cloud to render scenes in Spider-Man: Far From Home.https://storage.googleapis.com/gweb-cloudblog-publish/images/luma_spider-man.max-600x600.jpgGooglehttps://cloud.google.com/blog/products/compute/luma-pictures-render-spider-man-far-from-home-on-google-cloud/Todd PrivesProduct Manager, Cloud RenderingRuby support comes to App Engine standard environmenthttps://cloud.google.com/blog/products/application-development/ruby-support-comes-to-app-engine-standard-environment/<html><head></head><body><div class="block-paragraph"><div class="rich-text"><p>We have some exciting news for <a href="https://cloud.google.com/appengine/">App Engine</a> customers. Ruby is now Beta on App Engine standard environment, in addition to being available on the App Engine flexible environment. Let's dive into what that means if you’re a technical practitioner running your apps on Google Cloud. </p><p>There are lots of technical reasons to choose App Engine standard vs. flexible environment (<a href="https://cloud.google.com/appengine/docs/the-appengine-environments">this link explains it if you are curious</a>), but at a high level, App Engine standard environment brings a number of benefits to developers. For many users the most noticeable change is a decrease in deployment time from 4-7 minutes on App Engine flexible environment down to 1-3 minutes on App Engine standard. App Engine standard environment also supports scale-to-zero so you don't have to pay for your website when no one is using it. Finally, start-up time for new instances is measured in seconds rather than minutes—App Engine standard environment is simply more responsive to changes in load. </p><p>Scale-to-zero has its advantages in terms of cost, but it also means that you’ll want a truly serverless background processing architecture. For that, Cloud Pub/Sub and Cloud Tasks are great solutions for handling background tasks, and they also operate on a pay-per-use model. </p>We expect most Ruby developers to choose App Engine standard environment over App Engine flexible environment. The faster deployment time and scale-to-zero features are a huge benefit to most development processes. And deploying an existing Rails app to App Engine standard environment is pretty straightforward. But as they say, <a href="http://www.thagomizer.com/blog/2019/08/20/app-engine-updates-for-rubyists.html">your mileage may vary</a>. Look at the pros and cons in our <a href="https://cloud.google.com/appengine/docs/the-appengine-environments">documentation</a> to choose the right App Engine for your Ruby applications.<p></p></div></div></body></html>Tue, 27 Aug 2019 17:30:00 -0000https://cloud.google.com/blog/products/application-development/ruby-support-comes-to-app-engine-standard-environment/Application DevelopmentarticleRuby support comes to App Engine standard environmentSupport for Ruby is now generally available of App Engine standard environment.Googlehttps://cloud.google.com/blog/products/application-development/ruby-support-comes-to-app-engine-standard-environment/Morgan HallmonProduct ManagerBeyond the Map: A Q&A with engineering director Andrew Lookingbillhttps://cloud.google.com/blog/products/maps-platform/beyond-map-q-engineering-director-andrew-lookingbill/<html><head></head><body><div class="block-paragraph"><div class="rich-text"><p>Last month we kicked off “Beyond the Map”, a series of blog posts giving you a closer look at how we build maps that keep up with the changing world and that power apps, experiences, and businesses around the globe. In <a href="https://cloud.google.com/blog/products/maps-platform/beyond-the-map-how-we-build-the-maps-that-power-your-apps-and-business">our first post</a>, you heard about the key areas of our mapmaking processes–imagery, authoritative third-party data, community contributions, and machine learning.</p><p>In our next installment we’ll dive deeper into how we use imagery to map the world and what that means for our customers. But before we do that, we sat down with the co-author of the first post, engineering director Andrew Lookingbill, to learn more about his passion for mapmaking, biggest technical challenge at Google, and advice he has for developers working on all kinds of problems. </p><p><b>Of all the companies to work for, why did you choose Google and why have you stayed at Google for more than a decade? <br/></b>My coworkers and I who joined the Google Street View team came to Google because of the scope of the team’s ambitions. It’s easy, now that Street View has been around for a while, to forget how cheerfully mind-blowing the charter was. Sure, let’s take pictures–of everything–and make it possible to visit anywhere on the planet. There was something very attractive about that mindset.</p><p><b>What exactly do you and your team do at Google? <br/></b>Today my team and I focus on the algorithms, infrastructure, and tools we use to create and maintain our model of the real world. This includes all the imagery and 3D models, as well as all of the semantic data like addresses, business information, roads, natural features, buildings, etc. It’s an awesome job both because of the breadth of technical work–everything from building hardware for cars, to working on ML algorithms that can help make our maps better just by looking at pictures–and the breadth of use cases of the data.</p><p><b>Not only have you been at Google for more than a decade, but you’ve been on the Geo team for all that time. Haven’t you gotten bored of mapping the world yet?<br/></b>Google has a wonderful culture of internal mobility, and the fact that I’ve stayed very close to the same team I joined on my first day makes me a bit unusual. Two things have kept me here. The first, unsurprisingly, is the group of people I work with. I’ve never met a more impressive and humble group. The second is the size of the challenge we work on and the impact we can have. The world’s a big place, and it’s changing constantly. Mapping it is a task that’s never “done” and as new use cases for the data keep being imagined by developers inside and outside of Google, it just keeps getting more interesting.</p><p><b>What’s the biggest technical challenge you’ve faced at Google? <br/></b>When we first launched a country’s worth of Google-created and curated map data, the set of technical challenges involved in swapping out map data across all of our systems Google-wide, was probably the hardest, most ill-specified problem I’d ever worked on in my career up until that point. Though it’s a class of problem I’ve gotten to work on several times since. When you swap out the set of data that systems were built on and optimized for, you find all sorts of situations where the code was overfit for the existing data, and subtle differences crop up in downstream systems. For example, if you launch much more detailed geometry for water bodies, various assumptions about the memory required will break, etc. Similarly, swapping all the data out at once, in our live services, so users aren’t impacted by strangeness caused by one service (say routing) using different data than another (say search) without anyone noticing was so closely akin to pulling the tablecloth off a fully set table that we had to stop using that analogy.</p><p><b>How about the most unusual, unexpected, or funny challenge? <br/></b>One of the things I love about my career is that when you do new things, you get new challenges. Early in the Street View project, we were covering the cameras at night to protect them from dew, etc. Turns out a low-tech solution worked wonderfully–socks! The only problem was that every once in a while, someone would forget to take the sock off before they started driving. In the end, the team implemented a “sock detector” image processing algorithm that would quickly give the driver a warning if it thought the driver was driving with the sock still in place. Street View cars today are far more sophisticated, and no socks are required, so the sock detector is no more.  </p><p><b>What do you think the role of machine learning is in mapping the world? <br/></b>The role of machine learning in mapping is one of scale. Street View, processed and aligned aerial imagery, and satellite imagery are incredible because they allow a type of telepresence. You can glean information about a place in the world without actually physically being there, often enough to build a useful map. Machine learning has started to allow us to generate these insights without needing to, for instance, examine each Street View panorama for new business addresses. This in turn allows us to make useful maps for a much larger portion of the world’s population than would have been possible otherwise.</p><p><b>Have you ever driven a Street View car? What was it like?</b><br/>I did get a few opportunities to drive cars in the first fleet as we were building them. Even if we were just driving between buildings, it always attracted some attention, since cars with cameras strapped to the roof were a lot less common than they are today, even in Mountain View. I’ve definitely had a soft spot for Chevy Cobalts ever since. Funnily enough, part of our process for building out the cars involved removing the passenger seat to accommodate some hardware, so the extra seats tended to become de facto furniture in the building. Quite comfortable.</p><p><b>Back when Google launched Maps and Street View, it seemed like an audacious task. What advice do you have for engineers working on big ideas like these? <br/></b>Keep your eye on the forest and the trees. Breaking down an audacious goal into the component pieces that have to be built, and identifying metrics and tests to make sure you’re headed in the right direction are important. But periodically you need to reexamine the big picture, make sure you’re still on-track to hit your big goal, and that there aren’t other ways to get where you need to go.</p><p><b>Google Maps Platform has a wide spectrum of customers–from hobbyists to nonprofits to start-ups to Fortune 500 companies. And they’re all using our products in very different ways. What’s one tip you think can help any type of developer, working on any type of business or project? <br/></b>Talk to everyone. The teams I get to work with are inventive and happy to brainstorm about possible approaches. Especially early in your career, it can be daunting to come up against a problem it may take you days or weeks to even understand. Utilizing conversations with others to help make sense of it all and pressure-test ideas is one of the best things you can do to move past seemingly insurmountable obstacles.  </p><p><b>What's the one thing about our maps data that you don’t think people know or think about?<br/></b>That the map is, in many ways, a living thing–not a static description of the world. Things change all the time. Neighborhoods are built, businesses change, and so on. That vibrancy means that our users are a huge part of keeping the map fresh and useful for themselves. Local Guides and any user who knows something about the world that we’re missing or have wrong, can report the problem and help themselves and others have a better experience using the product. These community contributions are reflected in our consumer product and also shared with Google Maps Platform customers. So both consumers and customers are getting the most up to date information about the world that we can offer. </p><p><b>What do you hope to accomplish next at Google?<br/></b>Keep mapping the world. As it moves faster, so will we.</p><p><i>For more information on Google Maps Platform, <a href="https://cloud.google.com/maps-platform/">visit our website</a>. </i></p></div></div></body></html>Tue, 27 Aug 2019 16:00:00 -0000https://cloud.google.com/blog/products/maps-platform/beyond-map-q-engineering-director-andrew-lookingbill/Google Maps PlatformarticleBeyond the Map: A Q&A with engineering director Andrew LookingbillWe sat down with engineering director, Andrew Lookingbill, to learn more about his passion for mapmaking, biggest technical challenge at Google, and advice he has for developers working on all kinds of problems.https://storage.googleapis.com/gweb-cloudblog-publish/images/large-015-MAP-GOO1045-QandA-AndrewLookingbil.max-600x600.pngGooglehttps://cloud.google.com/blog/products/maps-platform/beyond-map-q-engineering-director-andrew-lookingbill/Andrew LookingbillEngineering DirectorCloud Text-to-Speech expands its number of voices by nearly 70%, now covering 32 languages and variantshttps://cloud.google.com/blog/products/ai-machine-learning/cloud-text-to-speech-expands-its-number-of-voices-now-covering-33-languages-and-variants/<html><head></head><body><div class="block-paragraph"><div class="rich-text"><p><i><b>Editor's Note:</b> We have updated this blog to accurately reflect supported languages and variants; Norwegian (Nynorsk) voices are not currently available.</i><br/></p><p>In February, we provided an <a href="https://cloud.google.com/blog/products/ai-machine-learning/making-ai-powered-speech-more-accessible-now-with-more-options-lower-prices-and-new-languages-and-voices">update</a> on how we’re expanding our support for new languages/variants and voices in <a href="https://cloud.google.com/text-to-speech/">Cloud Text-to-Speech</a>. Today, we’re adding to that progress by announcing:</p><ul><li>Voices in 11 new languages or variants, including Czech, English (India), Filipino, Finnish, Greek, Hindi, Hungarian, Indonesian, Mandarin Chinese (China), Modern Standard Arabic, and Vietnamese—bringing the list of total languages/variants available to 32. <p></p></li><li>76 new voices (now 187 in total) overall across all languages/variants, including 38 new <a href="https://deepmind.com/blog/wavenet-generative-model-raw-audio/">WaveNet</a> neural net-powered voices (now 95 in total). See the complete list <a href="https://cloud.google.com/text-to-speech/docs/voices">here</a>.<br/></li><li>Availability of at least one WaveNet voice in all 32 languages/variants.<br/></li></ul><p>With these updates, Cloud Text-to-Speech developers can now reach millions more people across numerous countries with their applications—with many more languages to come. This enables a broad range of use cases, including Contact Center AI virtual agents, interacting with IoT devices in cars and the home, and audio-enablement of books and other text-based content.<br/></p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="cloud text-to-speech languages.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/text-to-speech-regions.0873101016261842.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>Google Cloud Text-to-Speech runs on Google’s <a href="https://cloud.google.com/tpu/">Tensor Processing Units (TPUs)</a>—custom silicon chips that we designed from the ground up to accelerate machine learning and AI workloads. Our unique compute infrastructure, together with cutting-edge research, has allowed us to develop and deploy WaveNet voices much faster than is typical in the industry. Cloud Text-to-Speech launched a year and a half ago with 6 WaveNet voices in 1 language, and we now have 95 WaveNet voices in 33 languages.</p><p>Among the major public cloud platforms, Cloud Text-to-Speech now offers the most languages/variants with “natural” (neural net-powered) voices, and the most voices overall:</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="cloud text-to-speech voices.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/cloud_text-to-speech_voices_graph_29eoq0Y.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p><b>The WaveNet advantage<br/></b>When customers call into contact centers, use verbal commands with connected devices in cars or in their homes, or listen to audio conversions of text-based media, they increasingly expect a voice that sounds natural and human. Businesses that offer human-sounding voices offer the best experiences for their customers, and if that experience can also be provided in numerous languages and countries, that advantage becomes global. </p><p>WaveNet in Cloud Text-to-Speech makes that advantage possible without the need for vast investments in developing your own AI-powered speech synthesis. Based on neural-net technology, WaveNet creates natural-sounding voices, closing the perceived quality gap between speech synthesis and human speech in US English by 70% per Mean Opinion Score. The practical impact is that for most listeners, a WaveNet voice makes human/computer interaction a smooth and familiar experience.</p></div></div><div class="block-image_full_width"><div class="article-module h-c-page"><div class="h-c-grid"><figure class="article-image--large h-c-grid__col h-c-grid__col--6 h-c-grid__col--offset-3 "><img alt="WaveNet cloud text-to-speech.png" src="https://storage.googleapis.com/gweb-cloudblog-publish/images/WaveNet_cloud_text-to-speech.max-1000x1000.png"/></figure></div></div></div><div class="block-paragraph"><div class="rich-text"><p>The difference between a standard synthetic voice and a WaveNet one is pretty clear; just listen to some of the new voices for yourself:</p></div></div><div class="block-paragraph"><div class="rich-text"><p><b>English (India):</b><a href="https://storage.googleapis.com/speech-docs/tts/Audio%20samples/En-in/en-in-Std.wav">Standard Voice</a> vs <a href="https://storage.googleapis.com/speech-docs/tts/Audio%20samples/En-in/en-in-WaveNet.wav">WaveNet Voice</a><br/><b>Hungarian</b>: <a href="https://storage.googleapis.com/speech-docs/tts/Audio%20samples/hu-hu/hu-hu-Std.wav">Standard Voice</a> vs <a href="https://storage.googleapis.com/speech-docs/tts/Audio%20samples/hu-hu/hu-hu-WaveNet.wav">WaveNet Voice</a><br/><b>Vietnamese</b>: <a href="https://storage.googleapis.com/speech-docs/tts/Audio%20samples/vietnamese/vi-vn-Std.wav">Standard Voice</a> vs <a href="https://storage.googleapis.com/speech-docs/tts/Audio%20samples/vietnamese/vi-vn-WaveNet.wav">WaveNet Voice</a><br/><b>Mandarin Chinese</b>: <a href="https://storage.googleapis.com/speech-docs/tts/Audio%20samples/cmn-cn/cmn-cn-Std.wav">Standard Voice</a> vs <a href="https://storage.googleapis.com/speech-docs/tts/Audio%20samples/cmn-cn/cmn-cn-WaveNet.wav">WaveNet Voice</a><br/><b>Japanese</b>: <a href="https://storage.googleapis.com/speech-docs/tts/Audio%20samples/ja-jp/ja-jp-Std.wav">Standard Voice</a> vs <a href="https://storage.googleapis.com/speech-docs/tts/Audio%20samples/ja-jp/ja-jp-WaveNet.wav">WaveNet Voice</a><br/></p></div></div><div class="block-paragraph"><div class="rich-text"><p>For a demo using text of your choosing, test-drive the <a href="https://cloud.google.com/text-to-speech/">example UI</a> we built using the Cloud Text-to-Speech API.</p><p><b>Next steps<br/></b>Cloud Text-to-Speech is free to use up to the first million characters processed by the API, so it’s easy to get started by building a simple test/demo app using your own data. We look forward to seeing what you build!</p></div></div></body></html>Tue, 27 Aug 2019 15:00:00 -0000https://cloud.google.com/blog/products/ai-machine-learning/cloud-text-to-speech-expands-its-number-of-voices-now-covering-33-languages-and-variants/Google Cloud PlatformAI & Machine LearningarticleCloud Text-to-Speech expands its number of voices by nearly 70%, now covering 32 languages and variantsWith today’s updates, Cloud Text-to-Speech developers can now reach millions more people across numerous countries with their applications—with many more languages to come.https://storage.googleapis.com/gweb-cloudblog-publish/images/Cloud_Text-to-Speech.max-600x600.jpgGooglehttps://cloud.google.com/blog/products/ai-machine-learning/cloud-text-to-speech-expands-its-number-of-voices-now-covering-33-languages-and-variants/Dan AharonProduct Manager, SpeechNew report analyzes the future of workplace productivityhttps://cloud.google.com/blog/products/productivity-collaboration/new-report-analyzes-the-future-of-workplace-productivity/<html><head></head><body><div class="block-paragraph"><div class="rich-text"><p><i>TL;DR: we examined the future of work in a recent report. <a href="https://cloud.google.com/make-it-work">Download and read the findings</a>. </i></p><p>Look at the contemporary business landscape, and it seems like everything has changed in just a short amount of time. </p><p>Today’s mid-career professional may have been in high school when the World Wide Web made the Internet a big commercial proposition. She likely started her career just before the dotcom bust, and, for nearly two decades, has witnessed the advent of big data, mobile, artificial intelligence, cloud computing, robotics, ecommerce, social media and more. Alongside the advent of these shifts in tech, the “office” has also transformed. From closed doors to cubicles to open plan, from typewriters to email to instant messaging, each transformation occurred in search of better information sharing and problem solving. </p><p>Yet while it’s true that the world has changed, our ambitions as workers have not. The same things we’ve always wanted to get out of work remain: </p><ul><li>To be able to work fast, with fewer mind-numbing hassles in our day.</li><li>To be able to work smart, with quick access to the best possible information and the sharpest expertise.</li><li>To be able to chase the best ideas, and get our work recognized and improved for maximum impact.</li></ul><p>While technology has increased the number of people we can connect with and how readily we can access new information, these opportunities can at times look like new challenges, especially if you rely on dated tools in the workplace. <a href="https://www.insight.com/content/dam/insight-web/en_US/pdfs/hbr/hbr-the-connected-workforce-report.pdf?utm_campaign=WREC_180601_Q2_ac1147_The%20Connected%20Workforce:%20Maximizing%20Productivity,%20Creativity%20and%20Profitability.02.Converted&amp;utm_source=marketo&amp;utm_medium=email&amp;utm_content=main-cta-button&amp;refcode&amp;mkt_tok=eyJpIjoiWW1NeU1tVm1ZVEE1TkRJeiIsInQiOiJ1NUg3b3ZcL3RsVVBkMitGY1BCUGkyYzBWSWVhcmQzZGMrMUhQN3N5Y2xncExCNFwvSHhtN1ZNN3o3TnlMbGZTWW53VVJyYVBLd1V2WTgzQ1VzR0FcL2RCc2FtaDNNMnRQTUZKazl2dVJNYmI5aGZqejNyOVhiVGZ2UFdhTFlcLzdGcjAifQ%3D%3D">Nearly four in 10</a> U.S.-based business and IT leaders say their current systems make it harder, not easier, for their employees to work quickly. It’s like being asked to make carbon paper copies, when the rest of the world was first on email. </p><p>Google’s <a href="https://cloud.google.com/make-it-work">latest report</a> on the future of work examines challenges such as this, and how businesses can change their tools, workflows, and cultures to improve productivity and encourage innovation in the modern workplace. </p><p>One of the interesting things about Google is that it was one of the first great companies to grow up assuming the internet as part of life. Consequently, this paved the way for the arrival of web-based email systems like Gmail, and productivity software to drive location-agnostic collaboration, like Google Drive or Docs. If you look at how these tools now incorporate advanced security and artificial intelligence for faster task execution, you’ll see a deep reflection of how work—and the world—has changed. People use these tools, however, because they meet human needs that have not changed.</p><p><a href="https://cloud.google.com/make-it-work">Click here</a> to download Google’s full report on the future of work, collaboration and productivity.</p></div></div></body></html>Tue, 27 Aug 2019 13:00:00 -0000https://cloud.google.com/blog/products/productivity-collaboration/new-report-analyzes-the-future-of-workplace-productivity/G SuiteChrome EnterpriseDriveGmailDocsResearchProductivity & CollaborationarticleNew report analyzes the future of workplace productivityhttps://storage.googleapis.com/gweb-cloudblog-publish/images/Google_Beyond_Custom_Ink.max-600x600.jpgGooglehttps://cloud.google.com/blog/products/productivity-collaboration/new-report-analyzes-the-future-of-workplace-productivity/Quentin HardyHead of Editorial, Google Cloud \ No newline at end of file diff --git a/RSParser/Tests/RSParserTests/Resources/coco.html b/RSParser/Tests/RSParserTests/Resources/coco.html deleted file mode 100644 index c17abb0d4..000000000 --- a/RSParser/Tests/RSParserTests/Resources/coco.html +++ /dev/null @@ -1,2329 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - Review: 'Coco' Is Among Pixar's Best Movies in Years - The Atlantic - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - -
    - - -
    -
    -
    -

    Coco Is Among Pixar's Best Movies in Years

    - -

    Full of wit, music, and color, this Día de Muertos–themed tribute to family marks a return to form for the studio.

    - -
    -
    - - - -
    -
    - - A still from Pixar's new film 'Coco' - - - - - - -
    - -
    - -
    - - -
    - - - - - - - - - - - - - -
    -
    - - - - - - - - - - - - - -
    - - - - - - -
    -
    -
    - - - -
    - -
    - - -

    Well, that’s more like it. As someone who has written at some length about the decline of Pixar Studios since its acquisition by Disney, I am especially pleased to be proven wrong, even if only intermittently. The studio’s latest release, Coco, is one such occasion.

    Though Pixar has never acknowledged as much publicly, its cinematic philosophy (and business model) has shifted notably: Where the studio once aspired to excellence with every single picture—Pixar President Ed Catmull wrote an entire book expressing this ideal, Creativity Inc.—it now seems content to roll out a few profitable, hyper-merchandise-friendly sequels for every genuinely original feature it unveils. (To put it another way, the studio has shifted away from “creativity” and toward “inc.”)

    But if Finding Dory and Cars 3 are the price we must pay for a film such as Coco, then so be it. Pixar’s latest is up there with Inside Out among the studio’s best features in years—less complex than Pete Docter’s 2015 film, but perhaps a tad more emotionally resonant.

    Miguel Rivera (Anthony Gonzalez) is a 12-year-old boy in Mexico whose greatest desire in life is to be a musician like his idol, the mid-century legend Ernesto de la Cruz (Benjamin Bratt). Alas, Miguel’s great-great-grandmother was abandoned by her musician husband, and the Rivera family has enforced an iron-clad policy against music ever since. Instead, each subsequent generation has gone into the family business of making shoes. (Shades of Hermey, the toy-making elf who wished to become a dentist.)

    But could it be that de la Cruz was in fact Miguel’s long since written-off great-great-grandfather? That certainly appears to be the case. So in order to participate in a music competition on Día de Muertos, Miguel “borrows” de la Cruz’s famous guitar, his own having been smashed earlier in the day by his grandmother. But with the very first strum, Miguel is transported to the Land of the Dead. There, he meets departed members of his own family and ultimately, with the help of a trickster named Héctor (Gael García Bernal), de la Cruz himself.

    Directed by longtime Pixarian Lee Unkrich (Toy Story 3), the tale that unfolds from these beginnings is not terribly innovative (less so, for instance, than 2014’s similarly themed though less well-realized The Book of Life). But it is a tale told with considerable wit—this is one of Pixar’s funniest films—and genuine tenderness. There are a few nice twists and reversals along the way. And while the movie’s conclusion is not difficult to see coming, anyone whose heart is not warmed by it may wish to consult with an cardio-therapist.

    Befitting its subject, this is the most musical feature yet produced by Pixar, with songs co-written by Robert Lopez, of The Book of Mormon, Avenue Q, and Frozen fame. There are clever pop-cultural nuggets scattered throughout: a Mac Plus that is condemned as a “devil box” and smashed with a shoe; a gatehouse between the lands of the living and the dead that bears a distinct resemblance to the entrance to Disneyland; a hilariously avant-garde stage show put on by a deceased Frida Kahlo.

    But where Coco shines most brightly—literally—is in its vibrant visuals, which rely on a palette of fluorescent greens, blues, yellows, and oranges. In this telling, the Land of the Dead is not a fearsome place, but rather a never-ending skeleton party conducted in a glorious multi-tiered city that rises from sea-level houseboats to vast, imperious towers inhabited by celebrities such as de la Cruz—all of them connected by arched bridges and aerial trams.

    Does Coco rise to the heights of Pixar’s very best work? No. But it is a generous, heartfelt film, full of color and music, one that offers a timely Thanksgiving tribute to the intergenerational importance of family. Its very title lovingly derives from the name of Miguel’s oldest living relative, great-grandmother Mamá Coco, whose importance to the story only becomes clear late in the film.

    I wish the movie suggested that all was now well with Pixar, but warning signs are, if anything, multiplying. The studio’s next two films will be sequels, The Incredibles 2 and an utterly heretical “franchise reboot,” Toy Story 4. With Coco, even the customary delight of a Pixar short before the movie is missing: In its place is “Olaf’s Frozen Adventure,” which whatever its quality (it did not screen for critics) suggests that the studio is being ever-more subsumed into its Disney parent. And the interlocking news stories that Pixar guru John Lasseter is going on leave due to alleged inappropriate behavior and that Rashida Jones left Toy Story 4 over issues of diversity are depressing on almost every level imaginable.

    But those are issues to be addressed in the days to come. In the meantime, my advice is to round up the family, take them to Coco, and together give thanks.

    - -
    - -
    - - - - - - - - -
    -
    - - - - - - - - - - - -
    - - - - - - - - - - - - -
    -

    About the Author

    - - - -
      - -
    • - - - Christopher Orr - -
      - - Christopher Orr is a senior editor and the principal film critic at The Atlantic. He has written on movies for the New Republic, LA Weekly, Salon, and The New York Sun, and has worked as an editor for numerous publications. - - - -
      - - - - -
    • - -
    - -
    - -
    -
    -
    - - - -
    - -
    - - - - - - - - - - - - - -
    - - - - - -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - -
    - -
    - - -
    - - -
    -
    - - - -
    - - - - -
    - - - - - - - -
    - - - - - - - - - - -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/RSParser/Tests/RSParserTests/Resources/curt.json b/RSParser/Tests/RSParserTests/Resources/curt.json deleted file mode 100644 index 44a0686a4..000000000 --- a/RSParser/Tests/RSParserTests/Resources/curt.json +++ /dev/null @@ -1 +0,0 @@ -{"icon":"http:\/\/curtclifton.net\/style\/feedicon.png","user_comment":"This feed allows you to read the posts from this site in any feed reader that supports the JSON Feed format. To add this feed to your reader, copy the following URL — http:\/\/curtclifton.net\/feed.json — and add it your reader.","favicon":"http:\/\/curtclifton.net\/style\/touch-icon.png","description":"Full-text posts, generally related to software development on Apple’s platforms. Programming language geekery.","version":"https:\/\/jsonfeed.org\/version\/1","title":"curtclifton.net","items":[{"date_published":"2018-01-06T08:00","title":"Twitter Quitter","id":"twitter-quitter","content_html":"

    I’ve decided to close my Twitter account. William Van Hecke makes a convincing case<\/a> for its diminishing utility, and it’s clear that Jack<\/a> is more concerned with eyeballs than standards<\/a>.<\/p>\n\n

    I stopped regularly reading my timeline months ago. The few times I have dipped in, I’ve ended up angry or depressed. Despite occasional bright spots, there is always someone sharing the angst of the day. I read the news. I don’t need Twitter to make me more anxious. As such, I’ve only been using Twitter for cross posting from my micro.blog account<\/a> and responding to mentions. Slack<\/a> meets my social chat needs without the screaming-into-the-void that Twitter has become.<\/p>\n\n

    After some reflection, I’ve concluded that even posting to Twitter is just providing content to a platform for hate and anger. I can’t fix that problem, but I can stop contributing to the platform. And so I will.<\/p>\n\n

    I’m taking my Twitter account private. I’ll stop reading and (after this) posting to it. If you want to get in touch, please email<\/a>, iMessage, or drop a mention <\/span>@<\/span>curt<\/span><\/code> on micro.blog<\/a>. I’d also be happy for an invite to your Slack group or a friend request on Facebook<\/a>.<\/em> (While Facebook is also an addiction-exploiting attention hole, it provides much more control to users. The positives there outweigh the negatives.)<\/p>\n\n

    Be well. Find the good in the world. Peace.<\/p>\n","url":"http:\/\/www.curtclifton.net\/twitter-quitter"},{"date_published":"2017-12-15T08:00","title":"Next Actions","id":"next-actions","content_html":"

    After six and a half wonderful years, today is my last with the Omni Group. It’s been the joy and privilege of a lifetime to work with the great people at Omni. Care for others permeates the culture at Omni, from interpersonal interactions, to software design, to our amazing Support Humans. It’s been especially rewarding to contribute to OmniFocus<\/a>, an app that’s been invaluable to me personally and that helps many others achieve their goals.<\/p>\n\n

    While it’s difficult to say goodbye to all that, I have an opportunity to join a small fruit company in Cupertino working on iPad software for education. The role combines several of my passions: teaching, learning, mentoring, and building elegant software. I’m looking forward to joining my new team in January and making great things together.<\/p>\n\n

    I’ll miss the amazing Xcoders<\/a> community, but hope to make it back occasionally. And, of course, I’ll see you all when you come to San Jose for WWDC and related festivities. <\/p>\n\n

    It will be great to be able spend more time with friends in the Bay Area. The next few weeks will be a whirlwind with the holidays and moving, but if you’re in the area hit me up<\/a> in the new year and let’s get together.<\/p>\n","url":"http:\/\/www.curtclifton.net\/next-actions"},{"date_published":"2017-10-27T07:00","title":"These are a Few of My Stateful Machines","id":"these-are-a-few-of-my-stateful-machines","content_html":"

    I’m excited to be presenting at the inaugural Swift by Northwest<\/a> conference today. My talk is on state machines and how easy they are to implement in Swift.<\/p>\n\n