{"action":"created","comment":{"url":"https://api.github.com/repos/mattrubin/Authenticator/pulls/comments/50209344","id":50209344,"diff_hunk":"@@ -0,0 +1,135 @@\n+//\n+//  TokenStore.swift\n+//  Authenticator\n+//\n+//  Copyright (c) 2015 Authenticator authors\n+//\n+//  Permission is hereby granted, free of charge, to any person obtaining a copy\n+//  of this software and associated documentation files (the \"Software\"), to deal\n+//  in the Software without restriction, including without limitation the rights\n+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n+//  copies of the Software, and to permit persons to whom the Software is\n+//  furnished to do so, subject to the following conditions:\n+//\n+//  The above copyright notice and this permission notice shall be included in all\n+//  copies or substantial portions of the Software.\n+//\n+//  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n+//  SOFTWARE.\n+//\n+\n+import Foundation\n+import OneTimePassword\n+\n+class TokenStore {\n+    private let keychain = Keychain.sharedInstance\n+    private(set) var persistentTokens: [PersistentToken]\n+\n+    init() {\n+        do {\n+            let persistentTokenSet = try keychain.allPersistentTokens()\n+            let sortedIdentifiers = TokenStore.persistentIdentifiers()\n+\n+            persistentTokens = persistentTokenSet.sort({ (A, B) in\n+                let indexOfA = sortedIdentifiers.indexOf(A.identifier)\n+                let indexOfB = sortedIdentifiers.indexOf(B.identifier)\n+\n+                switch (indexOfA, indexOfB) {\n+                case (.Some(let iA), .Some(let iB)) where iA < iB:\n+                    return true\n+                default:\n+                    return false\n+                }\n+            })\n+\n+            if persistentTokens.count > sortedIdentifiers.count {\n+                // If lost tokens were found and appended, save the full list of tokens\n+                saveTokenOrder()\n+            }\n+        } catch {\n+            persistentTokens = []\n+            // TODO: Handle the token loading error\n+        }\n+    }\n+}\n+\n+extension TokenStore {\n+    // MARK: Actions\n+\n+    func addToken(token: Token) {\n+        do {\n+            let newPersistentToken = try keychain.addToken(token)\n+            persistentTokens.append(newPersistentToken)\n+            saveTokenOrder()\n+            // TODO: Scroll to the new token (added at the bottom)\n+        } catch {\n+            // TODO: Handle the addToken(_:) failure","path":"Authenticator/Source/TokenStore.swift","position":71,"original_position":71,"commit_id":"a3729edff7ec61e9f9505dcc6b40ef8835382bbe","original_commit_id":"a3729edff7ec61e9f9505dcc6b40ef8835382bbe","user":{"login":"houndci","id":6697940,"avatar_url":"https://avatars.githubusercontent.com/u/6697940?v=3","gravatar_id":"","url":"https://api.github.com/users/houndci","html_url":"https://github.com/houndci","followers_url":"https://api.github.com/users/houndci/followers","following_url":"https://api.github.com/users/houndci/following{/other_user}","gists_url":"https://api.github.com/users/houndci/gists{/gist_id}","starred_url":"https://api.github.com/users/houndci/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/houndci/subscriptions","organizations_url":"https://api.github.com/users/houndci/orgs","repos_url":"https://api.github.com/users/houndci/repos","events_url":"https://api.github.com/users/houndci/events{/privacy}","received_events_url":"https://api.github.com/users/houndci/received_events","type":"User","site_admin":false},"body":"Todo Violation: TODOs and FIXMEs should be avoided. (todo)","created_at":"2016-01-20T02:56:02Z","updated_at":"2016-01-20T02:56:02Z","html_url":"https://github.com/mattrubin/Authenticator/pull/83#discussion_r50209344","pull_request_url":"https://api.github.com/repos/mattrubin/Authenticator/pulls/83","_links":{"self":{"href":"https://api.github.com/repos/mattrubin/Authenticator/pulls/comments/50209344"},"html":{"href":"https://github.com/mattrubin/Authenticator/pull/83#discussion_r50209344"},"pull_request":{"href":"https://api.github.com/repos/mattrubin/Authenticator/pulls/83"}}},"pull_request":{"url":"https://api.github.com/repos/mattrubin/Authenticator/pulls/83","id":56558003,"html_url":"https://github.com/mattrubin/Authenticator/pull/83","diff_url":"https://github.com/mattrubin/Authenticator/pull/83.diff","patch_url":"https://github.com/mattrubin/Authenticator/pull/83.patch","issue_url":"https://api.github.com/repos/mattrubin/Authenticator/issues/83","number":83,"state":"open","locked":false,"title":"Split TokenStore from TokenList","user":{"login":"mattrubin","id":532638,"avatar_url":"https://avatars.githubusercontent.com/u/532638?v=3","gravatar_id":"","url":"https://api.github.com/users/mattrubin","html_url":"https://github.com/mattrubin","followers_url":"https://api.github.com/users/mattrubin/followers","following_url":"https://api.github.com/users/mattrubin/following{/other_user}","gists_url":"https://api.github.com/users/mattrubin/gists{/gist_id}","starred_url":"https://api.github.com/users/mattrubin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mattrubin/subscriptions","organizations_url":"https://api.github.com/users/mattrubin/orgs","repos_url":"https://api.github.com/users/mattrubin/repos","events_url":"https://api.github.com/users/mattrubin/events{/privacy}","received_events_url":"https://api.github.com/users/mattrubin/received_events","type":"User","site_admin":false},"body":"Split the data store and persistence functionality out of `TokenList` and into the new `TokenStore` class.\r\nConvert `TokenList` to a struct component in the same design as the form components. ","created_at":"2016-01-20T02:55:44Z","updated_at":"2016-01-20T02:56:02Z","closed_at":null,"merged_at":null,"merge_commit_sha":"4d6d2117090f20767c60e56e39e491f4369c5c74","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/mattrubin/Authenticator/pulls/83/commits","review_comments_url":"https://api.github.com/repos/mattrubin/Authenticator/pulls/83/comments","review_comment_url":"https://api.github.com/repos/mattrubin/Authenticator/pulls/comments{/number}","comments_url":"https://api.github.com/repos/mattrubin/Authenticator/issues/83/comments","statuses_url":"https://api.github.com/repos/mattrubin/Authenticator/statuses/a3729edff7ec61e9f9505dcc6b40ef8835382bbe","head":{"label":"mattrubin:token-list","ref":"token-list","sha":"a3729edff7ec61e9f9505dcc6b40ef8835382bbe","user":{"login":"mattrubin","id":532638,"avatar_url":"https://avatars.githubusercontent.com/u/532638?v=3","gravatar_id":"","url":"https://api.github.com/users/mattrubin","html_url":"https://github.com/mattrubin","followers_url":"https://api.github.com/users/mattrubin/followers","following_url":"https://api.github.com/users/mattrubin/following{/other_user}","gists_url":"https://api.github.com/users/mattrubin/gists{/gist_id}","starred_url":"https://api.github.com/users/mattrubin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mattrubin/subscriptions","organizations_url":"https://api.github.com/users/mattrubin/orgs","repos_url":"https://api.github.com/users/mattrubin/repos","events_url":"https://api.github.com/users/mattrubin/events{/privacy}","received_events_url":"https://api.github.com/users/mattrubin/received_events","type":"User","site_admin":false},"repo":{"id":12443591,"name":"Authenticator","full_name":"mattrubin/Authenticator","owner":{"login":"mattrubin","id":532638,"avatar_url":"https://avatars.githubusercontent.com/u/532638?v=3","gravatar_id":"","url":"https://api.github.com/users/mattrubin","html_url":"https://github.com/mattrubin","followers_url":"https://api.github.com/users/mattrubin/followers","following_url":"https://api.github.com/users/mattrubin/following{/other_user}","gists_url":"https://api.github.com/users/mattrubin/gists{/gist_id}","starred_url":"https://api.github.com/users/mattrubin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mattrubin/subscriptions","organizations_url":"https://api.github.com/users/mattrubin/orgs","repos_url":"https://api.github.com/users/mattrubin/repos","events_url":"https://api.github.com/users/mattrubin/events{/privacy}","received_events_url":"https://api.github.com/users/mattrubin/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/mattrubin/Authenticator","description":"Two-Factor Authentication Client for iOS","fork":false,"url":"https://api.github.com/repos/mattrubin/Authenticator","forks_url":"https://api.github.com/repos/mattrubin/Authenticator/forks","keys_url":"https://api.github.com/repos/mattrubin/Authenticator/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mattrubin/Authenticator/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mattrubin/Authenticator/teams","hooks_url":"https://api.github.com/repos/mattrubin/Authenticator/hooks","issue_events_url":"https://api.github.com/repos/mattrubin/Authenticator/issues/events{/number}","events_url":"https://api.github.com/repos/mattrubin/Authenticator/events","assignees_url":"https://api.github.com/repos/mattrubin/Authenticator/assignees{/user}","branches_url":"https://api.github.com/repos/mattrubin/Authenticator/branches{/branch}","tags_url":"https://api.github.com/repos/mattrubin/Authenticator/tags","blobs_url":"https://api.github.com/repos/mattrubin/Authenticator/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mattrubin/Authenticator/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mattrubin/Authenticator/git/refs{/sha}","trees_url":"https://api.github.com/repos/mattrubin/Authenticator/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mattrubin/Authenticator/statuses/{sha}","languages_url":"https://api.github.com/repos/mattrubin/Authenticator/languages","stargazers_url":"https://api.github.com/repos/mattrubin/Authenticator/stargazers","contributors_url":"https://api.github.com/repos/mattrubin/Authenticator/contributors","subscribers_url":"https://api.github.com/repos/mattrubin/Authenticator/subscribers","subscription_url":"https://api.github.com/repos/mattrubin/Authenticator/subscription","commits_url":"https://api.github.com/repos/mattrubin/Authenticator/commits{/sha}","git_commits_url":"https://api.github.com/repos/mattrubin/Authenticator/git/commits{/sha}","comments_url":"https://api.github.com/repos/mattrubin/Authenticator/comments{/number}","issue_comment_url":"https://api.github.com/repos/mattrubin/Authenticator/issues/comments{/number}","contents_url":"https://api.github.com/repos/mattrubin/Authenticator/contents/{+path}","compare_url":"https://api.github.com/repos/mattrubin/Authenticator/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mattrubin/Authenticator/merges","archive_url":"https://api.github.com/repos/mattrubin/Authenticator/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mattrubin/Authenticator/downloads","issues_url":"https://api.github.com/repos/mattrubin/Authenticator/issues{/number}","pulls_url":"https://api.github.com/repos/mattrubin/Authenticator/pulls{/number}","milestones_url":"https://api.github.com/repos/mattrubin/Authenticator/milestones{/number}","notifications_url":"https://api.github.com/repos/mattrubin/Authenticator/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mattrubin/Authenticator/labels{/name}","releases_url":"https://api.github.com/repos/mattrubin/Authenticator/releases{/id}","created_at":"2013-08-28T20:15:05Z","updated_at":"2016-01-11T19:58:24Z","pushed_at":"2016-01-20T02:55:45Z","git_url":"git://github.com/mattrubin/Authenticator.git","ssh_url":"git@github.com:mattrubin/Authenticator.git","clone_url":"https://github.com/mattrubin/Authenticator.git","svn_url":"https://github.com/mattrubin/Authenticator","homepage":"mattrubin.me/authenticator","size":15424,"stargazers_count":83,"watchers_count":83,"language":"Swift","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":12,"mirror_url":null,"open_issues_count":9,"forks":12,"open_issues":9,"watchers":83,"default_branch":"master"}},"base":{"label":"mattrubin:master","ref":"master","sha":"027a6077ba04e84618a8bb929ab0a36c618f1be4","user":{"login":"mattrubin","id":532638,"avatar_url":"https://avatars.githubusercontent.com/u/532638?v=3","gravatar_id":"","url":"https://api.github.com/users/mattrubin","html_url":"https://github.com/mattrubin","followers_url":"https://api.github.com/users/mattrubin/followers","following_url":"https://api.github.com/users/mattrubin/following{/other_user}","gists_url":"https://api.github.com/users/mattrubin/gists{/gist_id}","starred_url":"https://api.github.com/users/mattrubin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mattrubin/subscriptions","organizations_url":"https://api.github.com/users/mattrubin/orgs","repos_url":"https://api.github.com/users/mattrubin/repos","events_url":"https://api.github.com/users/mattrubin/events{/privacy}","received_events_url":"https://api.github.com/users/mattrubin/received_events","type":"User","site_admin":false},"repo":{"id":12443591,"name":"Authenticator","full_name":"mattrubin/Authenticator","owner":{"login":"mattrubin","id":532638,"avatar_url":"https://avatars.githubusercontent.com/u/532638?v=3","gravatar_id":"","url":"https://api.github.com/users/mattrubin","html_url":"https://github.com/mattrubin","followers_url":"https://api.github.com/users/mattrubin/followers","following_url":"https://api.github.com/users/mattrubin/following{/other_user}","gists_url":"https://api.github.com/users/mattrubin/gists{/gist_id}","starred_url":"https://api.github.com/users/mattrubin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mattrubin/subscriptions","organizations_url":"https://api.github.com/users/mattrubin/orgs","repos_url":"https://api.github.com/users/mattrubin/repos","events_url":"https://api.github.com/users/mattrubin/events{/privacy}","received_events_url":"https://api.github.com/users/mattrubin/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/mattrubin/Authenticator","description":"Two-Factor Authentication Client for iOS","fork":false,"url":"https://api.github.com/repos/mattrubin/Authenticator","forks_url":"https://api.github.com/repos/mattrubin/Authenticator/forks","keys_url":"https://api.github.com/repos/mattrubin/Authenticator/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mattrubin/Authenticator/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mattrubin/Authenticator/teams","hooks_url":"https://api.github.com/repos/mattrubin/Authenticator/hooks","issue_events_url":"https://api.github.com/repos/mattrubin/Authenticator/issues/events{/number}","events_url":"https://api.github.com/repos/mattrubin/Authenticator/events","assignees_url":"https://api.github.com/repos/mattrubin/Authenticator/assignees{/user}","branches_url":"https://api.github.com/repos/mattrubin/Authenticator/branches{/branch}","tags_url":"https://api.github.com/repos/mattrubin/Authenticator/tags","blobs_url":"https://api.github.com/repos/mattrubin/Authenticator/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mattrubin/Authenticator/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mattrubin/Authenticator/git/refs{/sha}","trees_url":"https://api.github.com/repos/mattrubin/Authenticator/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mattrubin/Authenticator/statuses/{sha}","languages_url":"https://api.github.com/repos/mattrubin/Authenticator/languages","stargazers_url":"https://api.github.com/repos/mattrubin/Authenticator/stargazers","contributors_url":"https://api.github.com/repos/mattrubin/Authenticator/contributors","subscribers_url":"https://api.github.com/repos/mattrubin/Authenticator/subscribers","subscription_url":"https://api.github.com/repos/mattrubin/Authenticator/subscription","commits_url":"https://api.github.com/repos/mattrubin/Authenticator/commits{/sha}","git_commits_url":"https://api.github.com/repos/mattrubin/Authenticator/git/commits{/sha}","comments_url":"https://api.github.com/repos/mattrubin/Authenticator/comments{/number}","issue_comment_url":"https://api.github.com/repos/mattrubin/Authenticator/issues/comments{/number}","contents_url":"https://api.github.com/repos/mattrubin/Authenticator/contents/{+path}","compare_url":"https://api.github.com/repos/mattrubin/Authenticator/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mattrubin/Authenticator/merges","archive_url":"https://api.github.com/repos/mattrubin/Authenticator/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mattrubin/Authenticator/downloads","issues_url":"https://api.github.com/repos/mattrubin/Authenticator/issues{/number}","pulls_url":"https://api.github.com/repos/mattrubin/Authenticator/pulls{/number}","milestones_url":"https://api.github.com/repos/mattrubin/Authenticator/milestones{/number}","notifications_url":"https://api.github.com/repos/mattrubin/Authenticator/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mattrubin/Authenticator/labels{/name}","releases_url":"https://api.github.com/repos/mattrubin/Authenticator/releases{/id}","created_at":"2013-08-28T20:15:05Z","updated_at":"2016-01-11T19:58:24Z","pushed_at":"2016-01-20T02:55:45Z","git_url":"git://github.com/mattrubin/Authenticator.git","ssh_url":"git@github.com:mattrubin/Authenticator.git","clone_url":"https://github.com/mattrubin/Authenticator.git","svn_url":"https://github.com/mattrubin/Authenticator","homepage":"mattrubin.me/authenticator","size":15424,"stargazers_count":83,"watchers_count":83,"language":"Swift","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":12,"mirror_url":null,"open_issues_count":9,"forks":12,"open_issues":9,"watchers":83,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/mattrubin/Authenticator/pulls/83"},"html":{"href":"https://github.com/mattrubin/Authenticator/pull/83"},"issue":{"href":"https://api.github.com/repos/mattrubin/Authenticator/issues/83"},"comments":{"href":"https://api.github.com/repos/mattrubin/Authenticator/issues/83/comments"},"review_comments":{"href":"https://api.github.com/repos/mattrubin/Authenticator/pulls/83/comments"},"review_comment":{"href":"https://api.github.com/repos/mattrubin/Authenticator/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/mattrubin/Authenticator/pulls/83/commits"},"statuses":{"href":"https://api.github.com/repos/mattrubin/Authenticator/statuses/a3729edff7ec61e9f9505dcc6b40ef8835382bbe"}}}}