{"action":"created","comment":{"url":"https://api.github.com/repos/sparkle-project/Sparkle/pulls/comments/37140585","id":37140585,"diff_hunk":"@@ -7,13 +7,165 @@\n //\n \n #import \"SUPlainInstaller.h\"\n-#import \"SUPlainInstallerInternals.h\"\n+#import \"SUFileManager.h\"\n #import \"SUCodeSigningVerifier.h\"\n #import \"SUConstants.h\"\n #import \"SUHost.h\"\n+#import \"SULog.h\"\n \n @implementation SUPlainInstaller\n \n+// Returns the bundle version from the specified host that is appropriate to use as a filename, or nil if we're unable to retrieve one\n++ (NSString *)bundleVersionAppropriateForFilenameFromHost:(SUHost *)host\n+{\n+    NSString *bundleVersion = [host objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey];\n+    NSString *trimmedVersion = @\"\";\n+    \n+    if (bundleVersion != nil) {\n+        NSMutableCharacterSet *validCharacters = [NSMutableCharacterSet alphanumericCharacterSet];\n+        [validCharacters formUnionWithCharacterSet:[NSCharacterSet characterSetWithCharactersInString:@\".-()\"]];\n+        \n+        trimmedVersion = [bundleVersion stringByTrimmingCharactersInSet:[validCharacters invertedSet]];\n+    }\n+    \n+    return trimmedVersion.length > 0 ? trimmedVersion : nil;\n+}\n+\n++ (BOOL)performInstallationToURL:(NSURL *)installationURL fromUpdateAtURL:(NSURL *)newURL withHost:(SUHost *)host error:(NSError * __autoreleasing *)error\n+{\n+    SUFileManager *fileManager = [[SUFileManager alloc] init];\n+    \n+    // Create a temporary directory for our new app that resides on our destination's volume\n+    NSURL *tempNewDirectoryURL = [fileManager makeTemporaryDirectoryWithPreferredName:[installationURL.lastPathComponent.stringByDeletingPathExtension stringByAppendingString:@\" (Incomplete Update)\"] appropriateForDirectoryURL:installationURL.URLByDeletingLastPathComponent error:error];","path":"Sparkle/SUPlainInstaller.m","position":34,"original_position":34,"commit_id":"5d53d7277cccd12260b6be0e6ae969055450697c","original_commit_id":"5d53d7277cccd12260b6be0e6ae969055450697c","user":{"login":"pornel","id":72159,"avatar_url":"https://avatars.githubusercontent.com/u/72159?v=3","gravatar_id":"","url":"https://api.github.com/users/pornel","html_url":"https://github.com/pornel","followers_url":"https://api.github.com/users/pornel/followers","following_url":"https://api.github.com/users/pornel/following{/other_user}","gists_url":"https://api.github.com/users/pornel/gists{/gist_id}","starred_url":"https://api.github.com/users/pornel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pornel/subscriptions","organizations_url":"https://api.github.com/users/pornel/orgs","repos_url":"https://api.github.com/users/pornel/repos","events_url":"https://api.github.com/users/pornel/events{/privacy}","received_events_url":"https://api.github.com/users/pornel/received_events","type":"User","site_admin":false},"body":"Since we're installing a single item, having a temp directory seems unnecessary.\r\n\r\nCould you just move `/tmp/foo.app` to `destination/foo (incomplete vX).app`? or maybe to `.foo (incomplete vX).app` (with a `.` prefix to prevent temp file from popping up in Finder)\r\n","created_at":"2015-08-15T23:41:26Z","updated_at":"2015-08-15T23:41:26Z","html_url":"https://github.com/sparkle-project/Sparkle/pull/575#discussion_r37140585","pull_request_url":"https://api.github.com/repos/sparkle-project/Sparkle/pulls/575","_links":{"self":{"href":"https://api.github.com/repos/sparkle-project/Sparkle/pulls/comments/37140585"},"html":{"href":"https://github.com/sparkle-project/Sparkle/pull/575#discussion_r37140585"},"pull_request":{"href":"https://api.github.com/repos/sparkle-project/Sparkle/pulls/575"}}},"pull_request":{"url":"https://api.github.com/repos/sparkle-project/Sparkle/pulls/575","id":40638406,"html_url":"https://github.com/sparkle-project/Sparkle/pull/575","diff_url":"https://github.com/sparkle-project/Sparkle/pull/575.diff","patch_url":"https://github.com/sparkle-project/Sparkle/pull/575.patch","issue_url":"https://api.github.com/repos/sparkle-project/Sparkle/issues/575","number":575,"state":"open","locked":false,"title":"Rewrite file operations for updating an app","user":{"login":"zorgiepoo","id":857267,"avatar_url":"https://avatars.githubusercontent.com/u/857267?v=3","gravatar_id":"","url":"https://api.github.com/users/zorgiepoo","html_url":"https://github.com/zorgiepoo","followers_url":"https://api.github.com/users/zorgiepoo/followers","following_url":"https://api.github.com/users/zorgiepoo/following{/other_user}","gists_url":"https://api.github.com/users/zorgiepoo/gists{/gist_id}","starred_url":"https://api.github.com/users/zorgiepoo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zorgiepoo/subscriptions","organizations_url":"https://api.github.com/users/zorgiepoo/orgs","repos_url":"https://api.github.com/users/zorgiepoo/repos","events_url":"https://api.github.com/users/zorgiepoo/events{/privacy}","received_events_url":"https://api.github.com/users/zorgiepoo/received_events","type":"User","site_admin":false},"body":"The improvements we want to achieve:\r\n* Stability - No more corrupt / incomplete updates by ensuring atomic move operations\r\n* Efficiency - Faster updates because of reduction of file copies that need to be done\r\n* Separation from Host and Helper - No usage of SUFileManager in the host application\r\n* Cleanness - Minimized deprecated APIs (no FSPathMakeRef, FSGetCatalogInfo, FSFindFolder, etc), use modern Cocoa APIs when possible, robust error handling, strong documentation, easier to read code.\r\n\r\nMany other small notes:\r\n* SUFileManager does not have class methods. Methods across an instance share an AuthorizationRef, instead of having to authorize every time a privileged operation needs to be done\r\n* The odd logic of `while (authStat == errAuthorizationDenied)` was removed\r\n* Instead of checking if we have write access, we try to invoke a normal operation first. If that doesn't work, with the right error returned back, we try authorizing, which is what Apple recommends doing\r\n* The trash is not used as a temporary location anymore. Rather we now request for a temporary directory to be created. This is more likely to succeed too (eg: on a drive that has no trash folder)\r\n* The old method for releasing the quarantine did not work until I used the \"com.apple.quarantine\" key instead of the LS constant that was being used.\r\n* For the old method for releasing the quarantine, we now check if a file has the quarantine xattr bit set before removing it. Just like how the new method works.\r\n* Quarantines can now be removed with authorization\r\n* The result of calling temporaryNameForPath: was never actually used in the old code\r\n* -[NSString fileSystemRepresentation] which can throw an exception is not used. Only getFileSystemRepresentation:maxLength: is used and the error on it is always checked\r\n* The desperate/hopeless check for if the code signature for the update is valid after the installation is complete is removed\r\n\r\nPotential issues that still needs to be looked in to:\r\n* ~~Currently the method to move a file to the trash only works on 10.8+~~\r\n* ~~Appending of version number / picking destination name of app moved to trash~~\r\n\r\n--\r\n\r\nThis is still a work in progress but most of the work is done. This may improve #441 and #558, and would resolve #498. This pull request is definitely a big change / should be reviewed carefully.","created_at":"2015-07-23T05:32:37Z","updated_at":"2015-08-15T23:41:26Z","closed_at":null,"merged_at":null,"merge_commit_sha":"3b2160091d632733ff3caa2730f3e5f49f720b61","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/sparkle-project/Sparkle/pulls/575/commits","review_comments_url":"https://api.github.com/repos/sparkle-project/Sparkle/pulls/575/comments","review_comment_url":"https://api.github.com/repos/sparkle-project/Sparkle/pulls/comments{/number}","comments_url":"https://api.github.com/repos/sparkle-project/Sparkle/issues/575/comments","statuses_url":"https://api.github.com/repos/sparkle-project/Sparkle/statuses/5d53d7277cccd12260b6be0e6ae969055450697c","head":{"label":"zorgiepoo:fileoperations","ref":"fileoperations","sha":"5d53d7277cccd12260b6be0e6ae969055450697c","user":{"login":"zorgiepoo","id":857267,"avatar_url":"https://avatars.githubusercontent.com/u/857267?v=3","gravatar_id":"","url":"https://api.github.com/users/zorgiepoo","html_url":"https://github.com/zorgiepoo","followers_url":"https://api.github.com/users/zorgiepoo/followers","following_url":"https://api.github.com/users/zorgiepoo/following{/other_user}","gists_url":"https://api.github.com/users/zorgiepoo/gists{/gist_id}","starred_url":"https://api.github.com/users/zorgiepoo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zorgiepoo/subscriptions","organizations_url":"https://api.github.com/users/zorgiepoo/orgs","repos_url":"https://api.github.com/users/zorgiepoo/repos","events_url":"https://api.github.com/users/zorgiepoo/events{/privacy}","received_events_url":"https://api.github.com/users/zorgiepoo/received_events","type":"User","site_admin":false},"repo":{"id":33382254,"name":"Sparkle","full_name":"zorgiepoo/Sparkle","owner":{"login":"zorgiepoo","id":857267,"avatar_url":"https://avatars.githubusercontent.com/u/857267?v=3","gravatar_id":"","url":"https://api.github.com/users/zorgiepoo","html_url":"https://github.com/zorgiepoo","followers_url":"https://api.github.com/users/zorgiepoo/followers","following_url":"https://api.github.com/users/zorgiepoo/following{/other_user}","gists_url":"https://api.github.com/users/zorgiepoo/gists{/gist_id}","starred_url":"https://api.github.com/users/zorgiepoo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zorgiepoo/subscriptions","organizations_url":"https://api.github.com/users/zorgiepoo/orgs","repos_url":"https://api.github.com/users/zorgiepoo/repos","events_url":"https://api.github.com/users/zorgiepoo/events{/privacy}","received_events_url":"https://api.github.com/users/zorgiepoo/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/zorgiepoo/Sparkle","description":"A software update framework for OS X","fork":true,"url":"https://api.github.com/repos/zorgiepoo/Sparkle","forks_url":"https://api.github.com/repos/zorgiepoo/Sparkle/forks","keys_url":"https://api.github.com/repos/zorgiepoo/Sparkle/keys{/key_id}","collaborators_url":"https://api.github.com/repos/zorgiepoo/Sparkle/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/zorgiepoo/Sparkle/teams","hooks_url":"https://api.github.com/repos/zorgiepoo/Sparkle/hooks","issue_events_url":"https://api.github.com/repos/zorgiepoo/Sparkle/issues/events{/number}","events_url":"https://api.github.com/repos/zorgiepoo/Sparkle/events","assignees_url":"https://api.github.com/repos/zorgiepoo/Sparkle/assignees{/user}","branches_url":"https://api.github.com/repos/zorgiepoo/Sparkle/branches{/branch}","tags_url":"https://api.github.com/repos/zorgiepoo/Sparkle/tags","blobs_url":"https://api.github.com/repos/zorgiepoo/Sparkle/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/zorgiepoo/Sparkle/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/zorgiepoo/Sparkle/git/refs{/sha}","trees_url":"https://api.github.com/repos/zorgiepoo/Sparkle/git/trees{/sha}","statuses_url":"https://api.github.com/repos/zorgiepoo/Sparkle/statuses/{sha}","languages_url":"https://api.github.com/repos/zorgiepoo/Sparkle/languages","stargazers_url":"https://api.github.com/repos/zorgiepoo/Sparkle/stargazers","contributors_url":"https://api.github.com/repos/zorgiepoo/Sparkle/contributors","subscribers_url":"https://api.github.com/repos/zorgiepoo/Sparkle/subscribers","subscription_url":"https://api.github.com/repos/zorgiepoo/Sparkle/subscription","commits_url":"https://api.github.com/repos/zorgiepoo/Sparkle/commits{/sha}","git_commits_url":"https://api.github.com/repos/zorgiepoo/Sparkle/git/commits{/sha}","comments_url":"https://api.github.com/repos/zorgiepoo/Sparkle/comments{/number}","issue_comment_url":"https://api.github.com/repos/zorgiepoo/Sparkle/issues/comments{/number}","contents_url":"https://api.github.com/repos/zorgiepoo/Sparkle/contents/{+path}","compare_url":"https://api.github.com/repos/zorgiepoo/Sparkle/compare/{base}...{head}","merges_url":"https://api.github.com/repos/zorgiepoo/Sparkle/merges","archive_url":"https://api.github.com/repos/zorgiepoo/Sparkle/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/zorgiepoo/Sparkle/downloads","issues_url":"https://api.github.com/repos/zorgiepoo/Sparkle/issues{/number}","pulls_url":"https://api.github.com/repos/zorgiepoo/Sparkle/pulls{/number}","milestones_url":"https://api.github.com/repos/zorgiepoo/Sparkle/milestones{/number}","notifications_url":"https://api.github.com/repos/zorgiepoo/Sparkle/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/zorgiepoo/Sparkle/labels{/name}","releases_url":"https://api.github.com/repos/zorgiepoo/Sparkle/releases{/id}","created_at":"2015-04-03T20:50:34Z","updated_at":"2015-04-20T04:31:22Z","pushed_at":"2015-08-15T23:27:54Z","git_url":"git://github.com/zorgiepoo/Sparkle.git","ssh_url":"git@github.com:zorgiepoo/Sparkle.git","clone_url":"https://github.com/zorgiepoo/Sparkle.git","svn_url":"https://github.com/zorgiepoo/Sparkle","homepage":"http://sparkle-project.org","size":13229,"stargazers_count":0,"watchers_count":0,"language":"Objective-C","has_issues":false,"has_downloads":false,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"sparkle-project:master","ref":"master","sha":"d3aed95e39cb4676ef2cebd5a9d05c97b3a2960f","user":{"login":"sparkle-project","id":7999168,"avatar_url":"https://avatars.githubusercontent.com/u/7999168?v=3","gravatar_id":"","url":"https://api.github.com/users/sparkle-project","html_url":"https://github.com/sparkle-project","followers_url":"https://api.github.com/users/sparkle-project/followers","following_url":"https://api.github.com/users/sparkle-project/following{/other_user}","gists_url":"https://api.github.com/users/sparkle-project/gists{/gist_id}","starred_url":"https://api.github.com/users/sparkle-project/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sparkle-project/subscriptions","organizations_url":"https://api.github.com/users/sparkle-project/orgs","repos_url":"https://api.github.com/users/sparkle-project/repos","events_url":"https://api.github.com/users/sparkle-project/events{/privacy}","received_events_url":"https://api.github.com/users/sparkle-project/received_events","type":"Organization","site_admin":false},"repo":{"id":274782,"name":"Sparkle","full_name":"sparkle-project/Sparkle","owner":{"login":"sparkle-project","id":7999168,"avatar_url":"https://avatars.githubusercontent.com/u/7999168?v=3","gravatar_id":"","url":"https://api.github.com/users/sparkle-project","html_url":"https://github.com/sparkle-project","followers_url":"https://api.github.com/users/sparkle-project/followers","following_url":"https://api.github.com/users/sparkle-project/following{/other_user}","gists_url":"https://api.github.com/users/sparkle-project/gists{/gist_id}","starred_url":"https://api.github.com/users/sparkle-project/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sparkle-project/subscriptions","organizations_url":"https://api.github.com/users/sparkle-project/orgs","repos_url":"https://api.github.com/users/sparkle-project/repos","events_url":"https://api.github.com/users/sparkle-project/events{/privacy}","received_events_url":"https://api.github.com/users/sparkle-project/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/sparkle-project/Sparkle","description":"A software update framework for OS X","fork":false,"url":"https://api.github.com/repos/sparkle-project/Sparkle","forks_url":"https://api.github.com/repos/sparkle-project/Sparkle/forks","keys_url":"https://api.github.com/repos/sparkle-project/Sparkle/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sparkle-project/Sparkle/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sparkle-project/Sparkle/teams","hooks_url":"https://api.github.com/repos/sparkle-project/Sparkle/hooks","issue_events_url":"https://api.github.com/repos/sparkle-project/Sparkle/issues/events{/number}","events_url":"https://api.github.com/repos/sparkle-project/Sparkle/events","assignees_url":"https://api.github.com/repos/sparkle-project/Sparkle/assignees{/user}","branches_url":"https://api.github.com/repos/sparkle-project/Sparkle/branches{/branch}","tags_url":"https://api.github.com/repos/sparkle-project/Sparkle/tags","blobs_url":"https://api.github.com/repos/sparkle-project/Sparkle/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sparkle-project/Sparkle/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sparkle-project/Sparkle/git/refs{/sha}","trees_url":"https://api.github.com/repos/sparkle-project/Sparkle/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sparkle-project/Sparkle/statuses/{sha}","languages_url":"https://api.github.com/repos/sparkle-project/Sparkle/languages","stargazers_url":"https://api.github.com/repos/sparkle-project/Sparkle/stargazers","contributors_url":"https://api.github.com/repos/sparkle-project/Sparkle/contributors","subscribers_url":"https://api.github.com/repos/sparkle-project/Sparkle/subscribers","subscription_url":"https://api.github.com/repos/sparkle-project/Sparkle/subscription","commits_url":"https://api.github.com/repos/sparkle-project/Sparkle/commits{/sha}","git_commits_url":"https://api.github.com/repos/sparkle-project/Sparkle/git/commits{/sha}","comments_url":"https://api.github.com/repos/sparkle-project/Sparkle/comments{/number}","issue_comment_url":"https://api.github.com/repos/sparkle-project/Sparkle/issues/comments{/number}","contents_url":"https://api.github.com/repos/sparkle-project/Sparkle/contents/{+path}","compare_url":"https://api.github.com/repos/sparkle-project/Sparkle/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sparkle-project/Sparkle/merges","archive_url":"https://api.github.com/repos/sparkle-project/Sparkle/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sparkle-project/Sparkle/downloads","issues_url":"https://api.github.com/repos/sparkle-project/Sparkle/issues{/number}","pulls_url":"https://api.github.com/repos/sparkle-project/Sparkle/pulls{/number}","milestones_url":"https://api.github.com/repos/sparkle-project/Sparkle/milestones{/number}","notifications_url":"https://api.github.com/repos/sparkle-project/Sparkle/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sparkle-project/Sparkle/labels{/name}","releases_url":"https://api.github.com/repos/sparkle-project/Sparkle/releases{/id}","created_at":"2009-08-11T07:05:25Z","updated_at":"2015-08-14T07:25:24Z","pushed_at":"2015-08-15T23:27:55Z","git_url":"git://github.com/sparkle-project/Sparkle.git","ssh_url":"git@github.com:sparkle-project/Sparkle.git","clone_url":"https://github.com/sparkle-project/Sparkle.git","svn_url":"https://github.com/sparkle-project/Sparkle","homepage":"http://sparkle-project.org","size":15462,"stargazers_count":2165,"watchers_count":2165,"language":"Objective-C","has_issues":true,"has_downloads":false,"has_wiki":true,"has_pages":false,"forks_count":499,"mirror_url":null,"open_issues_count":148,"forks":499,"open_issues":148,"watchers":2165,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/sparkle-project/Sparkle/pulls/575"},"html":{"href":"https://github.com/sparkle-project/Sparkle/pull/575"},"issue":{"href":"https://api.github.com/repos/sparkle-project/Sparkle/issues/575"},"comments":{"href":"https://api.github.com/repos/sparkle-project/Sparkle/issues/575/comments"},"review_comments":{"href":"https://api.github.com/repos/sparkle-project/Sparkle/pulls/575/comments"},"review_comment":{"href":"https://api.github.com/repos/sparkle-project/Sparkle/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/sparkle-project/Sparkle/pulls/575/commits"},"statuses":{"href":"https://api.github.com/repos/sparkle-project/Sparkle/statuses/5d53d7277cccd12260b6be0e6ae969055450697c"}}}}