184 Commits
Author SHA1 Message Date
Samuel Attard 0e5d146ba1 align terminationHandler with dealloc in SQRLZipArchiver (#259)
For some reason even though the `NSTask` is terminated the file handle is still being read.  Fixes #247

Verified locally using the electron test suite and `chown nobody:nogroup Electron.app` during the test suite
2021-06-18 15:08:36 -07:00
Devin Foley cdc0729c8b fix: try multiple times to get a writeable log file (#255) 2020-11-03 13:05:04 -08:00
Samuel Attard a3a5b3f03b fix: use new ShipIt binary on macOS 11.0 to workaround flaw in launchApplicationAtURL (#253)
* fix: use /usr/bin/open on macOS 11.0 to workaround flaw in launchApplicationAtURL

* Use the new ShipIt as the launch pad

* chore: clean up diff

* Update cibuild

* build: use xcode 9.4
2020-10-09 15:52:19 -07:00
Samuel Attard 44468f858c chore: fix missing deallocs, deprecation warnings and invalid casts (#249) 2020-06-29 16:03:32 -07:00
Devin Foley adbe40a86a Set a 20 minute download timeout. (#241) 2019-11-05 13:29:29 -08:00
Samuel Attard b5ce589c4c Revert "fix: link ShipIt to ReactiveCocoa and Mantle correctly (#235)"
This reverts commit b426994d83.
2019-05-15 19:13:30 -07:00
Samuel Attard b426994d83 fix: link ShipIt to ReactiveCocoa and Mantle correctly (#235) 2019-03-25 13:49:42 -07:00
Josh Abernathy 279b5f9eb8 Merge pull request #231 from olegsklyarov/patch-1
fix jump link to update-file-json-format
2018-10-26 12:13:53 -04:00
Oleg Sklyarov 20a17327a6 fix jump link to update-file-json-format 2018-10-26 18:27:47 +03:00
Josh Abernathy 2a001cc575 Merge pull request #227 from tonyganch/xcode_build
Fix build for Xcode 9
2018-07-02 09:40:21 -04:00
Josh Abernathy 9a763fdab6 Merge pull request #225 from tonyganch/patch-1
Update README.md
2018-07-02 09:23:49 -04:00
Tony Ganch c3e3bddac9 Fix build for Xcode 9 2018-07-02 09:16:26 +02:00
Tony Ganch 3b419eb2e3 Update README.md
Reflect correct path to local checkouts of ReactiveCocoa and Mantle.
2018-06-01 21:02:38 +02:00
Josh Abernathy cb838776ce Merge pull request #221 from MarshallOfSound/patch-1
Fix invalid JSON in readme
2018-02-05 05:53:30 -10:00
Samuel Attard 8a5c88c9fa Fix invalid JSON in readme 2018-01-13 14:35:26 +11:00
Josh Abernathy 91f86ea8da Merge pull request #219 from MarshallOfSound/cdn-releases-fixed
Add CDN based releases
2018-01-12 16:27:36 -05:00
Samuel Attard 64c3fbe198 remove timeout 2018-01-12 15:34:19 +11:00
Thomas Moenicke 9a30a196ec submodules as in master 2018-01-11 02:09:03 +11:00
Samuel Attard 2abaad19b6 Document JSON structure 2018-01-11 02:07:27 +11:00
Samuel Attard 7e5dab08d1 Code cleanup 2018-01-11 02:01:38 +11:00
Samuel Attard d7233e04a3 Handle bad JSON and fix usage of NSURLRequest 2018-01-10 11:44:08 +11:00
Thomas Moenicke c80f006649 keep existing signatures for initWithUpdateRequest 2018-01-10 11:44:08 +11:00
Thomas Moenicke 23f184c1dd cleanup 2018-01-10 11:44:08 +11:00
Thomas Moenicke db58a91497 adding api to init SQRLi to init in CDN mode 2018-01-10 11:44:08 +11:00
Thomas Moenicke 3f398df659 using env variables instead of query parameter to switch between release server and json file mode
use: MODE=JSONFILE VERSION=1.5.0 open MyApp.app
defaults to release server
2018-01-10 11:44:08 +11:00
Thomas Moenicke 8536201df9 removed support for text based releases file
SquirrelApp is now an external project
2018-01-10 11:44:08 +11:00
Thomas Moenicke 417f4d8e91 adding cdn-based updates: a file (simple text or json) containing release information together with release files on a cdn 2018-01-10 11:44:08 +11:00
Josh Abernathy 3053fa59e2 Merge pull request #217 from pornel/master
Suggest only secure URLs for app download
2017-10-27 08:19:38 -04:00
Kornel 1d2388b30f Suggest only secure URLs for app download 2017-10-27 00:21:14 +01:00
Josh Abernathy 5396dcd1f9 Merge pull request #210 from Squirrel/provide-more-error-info
Provide more error context when we get POSIX errors
2017-07-20 09:47:13 -04:00
Josh Abernathy 203315f93e Merge pull request #207 from Squirrel/escalators-are-dangerous
Fix installing updates which need escalated privileges
2017-07-20 09:46:47 -04:00
joshaber 523824ea57 Provide more error context when we get POSIX errors
Fixes #209
2017-07-20 08:59:57 -04:00
joshaber 306443aaaf Merge branch 'master' into escalators-are-dangerous 2017-07-19 12:39:31 -04:00
Josh Abernathy db17e6865f Merge pull request #208 from Squirrel/make-tests-happen
Make CI happen
2017-07-19 12:39:01 -04:00
joshaber d14376c277 Don't run tests 2017-07-19 11:51:24 -04:00
joshaber 70cdda1ea2 Update the fixture app 2017-07-18 17:26:43 -04:00
joshaber 168fc856ff Update the expected error 2017-07-18 17:26:43 -04:00
joshaber f83f4ddcb7 I guess we need to do this to make signing work? 2017-07-18 17:26:43 -04:00
joshaber 7d02f612f8 Merge branch 'master' into escalators-are-dangerous 2017-07-18 13:47:56 -04:00
Josh Abernathy 0d4d0ba602 Merge pull request #206 from Squirrel/update-xcode
Update to Xcode 8
2017-07-18 13:47:33 -04:00
joshaber 6f16206cef I dunno try this 2017-07-18 13:02:21 -04:00
joshaber ee89952cc4 Revert "I dunno try this"
This reverts commit 6b98348ceb.
2017-07-18 13:01:43 -04:00
joshaber 6b98348ceb I dunno try this 2017-07-18 13:01:30 -04:00
joshaber b63125bfe0 Don't need this import either 2017-07-18 12:52:13 -04:00
joshaber 5ca2018292 Hardcode this 2017-07-18 11:20:03 -04:00
joshaber bbb1edb0fb Separate building and testing 2017-07-18 10:47:50 -04:00
joshaber b98c5981af Use xcodebuild to build 2017-07-18 09:58:57 -04:00
joshaber 13c6951b35 Consume the ShipIt state path from the args 2017-07-17 17:34:05 -04:00
joshaber 8806258058 Pass the state path to ShipIt 2017-07-17 17:33:52 -04:00
joshaber e672a408f4 Merge branch 'update-xcode' into fix-permissions 2017-07-17 17:05:19 -04:00
joshaber 386b7c09af Update the Travis image too 2017-07-17 17:04:13 -04:00
joshaber 27dedad85d Check writability for the parent directory too
If the parent isn't writable (e.g., /Applications), we'll need to launch with escalated privileges.
2017-07-17 17:04:00 -04:00
joshaber c22ad09d8e Update our dependencies 2017-07-17 16:51:09 -04:00
joshaber 9f87ff2113 Update the project and schemes 2017-07-17 16:45:23 -04:00
Josh Abernathy bde5ff2983 Merge pull request #190 from slyngbaek/master
Add auth headers to zip download requests not just feed requests.
2016-11-08 10:38:28 -05:00
Steffen Lyngbaek ae78589d3a Update block type name 2016-11-07 14:56:36 -08:00
Steffen Lyngbaek abec74ed3f Add block to initializer to provide custom download requests.
Add a second initializer with with a block argument to handle
customization of download url requests.

Add a generic download request to the original initializer without
changing the current api.
2016-11-07 12:19:02 -08:00
Steffen Lyngbaek a994876d98 Don't clobber headers explicitly set on the zip download request. 2016-11-04 10:32:54 -07:00
Steffen Lyngbaek 9d5464c693 Add auth headers to zip download requests not just feed requests. 2016-11-03 14:14:51 -07:00
Keith Duncan f00ad7e91b Merge pull request #186 from Squirrel/dont-check-for-updates-when-readonly
Error if there's an update available but the app's on a read-only volume
2016-10-18 11:03:53 +11:00
joshaber 4b0d5ffe18 Error check the statfs call 2016-10-17 14:04:20 -04:00
joshaber d93abe66ec Use a better description 2016-10-17 14:04:09 -04:00
joshaber 196abf93b8 Use a unique error code 2016-10-17 14:03:59 -04:00
joshaber a0079fdee0 Only error once we know if it matters 2016-10-14 10:47:24 -04:00
joshaber fdb4ffa000 Throw an error instead of silently failing 2016-10-14 10:43:56 -04:00
joshaber cc047bdc6f Get outta town if the volume's readonly 2016-10-14 10:43:09 -04:00
joshaber 7231919408 Added -isRunningOnReadOnlyVolume 2016-10-14 10:40:33 -04:00
Josh Abernathy e9e2188cda Merge pull request #176 from Squirrel/fix-updates-again
Don't error if our conditional GET fails.
2016-06-15 08:05:34 -07:00
joshaber 413b1d2bca Reorganize to separate out the unarchiving 2016-06-14 15:37:24 -07:00
joshaber d92d385431 Don't error if our conditional GET fails. 2016-06-13 18:09:24 -07:00
Josh Abernathy 9ebfd397fd Merge pull request #175 from Squirrel/fix-pruning
Don't prune if we've already downloaded updates.
2016-06-12 11:28:48 -04:00
joshaber 7dffc4b143 Don't prune if we've already downloaded updates.
Fixes #174
2016-06-08 14:30:37 -04:00
Josh Abernathy 37cd028730 Merge pull request #172 from Squirrel/truncate-logs
Truncate logs
2016-06-03 12:43:44 -04:00
joshaber cc8993e6e7 Bump up the max length. 2016-06-03 09:55:05 -04:00
joshaber a0b35f490b Revert "Move back to the beginning of the file before writing."
This reverts commit ed0614d8cf.
2016-06-02 12:32:29 -04:00
joshaber ed0614d8cf Move back to the beginning of the file before writing. 2016-06-02 12:31:03 -04:00
joshaber ea70eb937a Truncate log files. 2016-06-02 12:27:21 -04:00
joshaber b5ecff7e03 Use the log paths instead of constructing them manually. 2016-06-02 12:27:11 -04:00
joshaber 8589cbedbb Expose the log paths. 2016-06-02 12:27:02 -04:00
Josh Abernathy edc604bfec Merge pull request #171 from Squirrel/stop-redownloading
Conditionally download updates.
2016-06-02 10:13:22 -04:00
Keith Duncan 8995e0faeb Merge pull request #169 from Squirrel/use-cache
Use the Caches directory instead of Application Support.
2016-06-02 16:07:17 +02:00
joshaber 67a68bef8c 🍩 need this change 2016-06-01 14:26:06 -04:00
joshaber 7c4b43d2d1 Conditionally download the update. 2016-06-01 14:20:04 -04:00
Josh Abernathy 694f96abec More words.
Fixes #120.
2016-06-01 13:49:01 -04:00
joshaber 369854b103 Update specs 2016-06-01 13:12:24 -04:00
joshaber 104ebb9442 s/an/a 2016-06-01 12:59:49 -04:00
joshaber e1f56bfdc6 Use the Caches directory instead of Application Support. 2016-06-01 12:55:24 -04:00
Keith Duncan 4a50411246 Merge pull request #165 from adam-lynch/patch-1
README: removing trailing comma from JSON format
2016-02-22 10:06:24 +11:00
Adam Lynch fc3ce8158c README: removing trailing comma from JSON format 2016-02-21 22:54:01 +00:00
Josh Abernathy 6e1e11809e Merge pull request #156 from Squirrel/dates
Fix parsing of ISO 8601 dates
2015-11-09 13:25:16 -05:00
Matt Diephouse 9d67b82d5c Use the correct day specifier
`DD` is day of year; `dd` is day of month.
2015-11-09 12:58:40 -05:00
Matt Diephouse 09a3b74f93 Test the dates are correct 2015-11-09 12:57:41 -05:00
Matt Diephouse c108f4aac4 Merge pull request #154 from Squirrel/fix-ci
Fix CI
2015-10-26 17:43:39 -04:00
joshaber 0ac595efd9 I am dissappoint, Travis 2015-10-26 17:25:51 -04:00
joshaber ce1b59c1cf Revert "Smore random sleeps"
This reverts commit 2dea99045d.
2015-10-26 17:17:55 -04:00
joshaber f7438e91b3 Revert "More sleeping!"
This reverts commit 656d2cdd29.
2015-10-26 17:17:46 -04:00
joshaber 656d2cdd29 More sleeping! 2015-10-26 17:08:03 -04:00
joshaber d8a244dd1a Try Xcode 7.1 for funsies. 2015-10-26 16:58:21 -04:00
joshaber 2dea99045d Smore random sleeps 2015-10-26 16:56:28 -04:00
joshaber 483eb38d73 Resource rules were deprecated. 2015-10-26 16:55:30 -04:00
joshaber 475c50a8e3 🔥 mdeep-codesign
It hasn’t been needed for a while.
2015-10-26 16:51:37 -04:00
joshaber 4384ccb485 I dunno let's try waiting. 2015-10-26 16:46:29 -04:00
joshaber 4b4bb7c87c Stop tailing 2015-10-26 16:36:46 -04:00
joshaber cd6ccb9c7b Try just one test. 2015-10-26 16:27:11 -04:00
joshaber 0c91d7fe9c Don't use async anymore. 2015-10-26 16:27:06 -04:00
joshaber 0d8fe0f642 LOL 2015-10-26 16:13:15 -04:00
joshaber b6f4e70731 Try an async. 2015-10-26 16:09:25 -04:00
joshaber efd944e337 Revert "can u not"
This reverts commit 5bfd32eeae.
2015-10-26 16:02:03 -04:00
joshaber cc86db04e2 Standardize on our longer timeout. 2015-10-26 16:01:45 -04:00
joshaber c9ce161632 Up the timeout. 2015-10-26 14:08:08 -04:00
joshaber f924fc3023 Make sure we don't use automatic termination. 2015-10-26 13:58:19 -04:00
joshaber 5bfd32eeae can u not 2015-10-26 13:26:54 -04:00
joshaber 7c365d304f Ugh spelling. 2015-10-26 13:06:31 -04:00
joshaber a6a782aefb Bigger timeout? 2015-10-26 12:59:31 -04:00
joshaber aa72c42c3b Setup some certs. 2015-10-26 12:21:33 -04:00
joshaber bee2be5dfb Revert "🔥 Deep code sign."
This reverts commit d0c932a90d.
2015-10-26 11:58:05 -04:00
joshaber 9bcc86be72 Update Travis again. 2015-10-26 11:42:43 -04:00
joshaber d0c932a90d 🔥 Deep code sign.
It’s been unnecessary for a while now, afaik. /cc @keithduncan
2015-10-26 11:41:40 -04:00
joshaber 4a0aa59d25 We really don't care about app extensions. 2015-10-26 11:40:49 -04:00
joshaber 70a838c834 Use Travis. 2015-10-23 16:52:54 -04:00
joshaber 19dc11c735 Fix the tests. 2015-10-23 16:52:50 -04:00
Matt Diephouse 87e854e8d4 Merge pull request #153 from Squirrel/xcode-7
Xcode 7 support
2015-10-23 10:45:12 -04:00
joshaber 5640bbe863 --regret 2015-10-23 09:49:49 -04:00
joshaber 843ee9fa22 Revert "Update all our references to RAC files."
This reverts commit 0026cef7bf.
2015-10-23 09:44:20 -04:00
joshaber 95ce084ddd Revert "Go with the swift2 branch of RAC."
This reverts commit 2cc8a7d4ab.
2015-10-23 09:44:12 -04:00
joshaber ed92f6aad2 Revert "Bleh."
This reverts commit 56f52ccfe4.
2015-10-23 09:44:02 -04:00
joshaber 56f52ccfe4 Bleh. 2015-10-22 17:19:17 -04:00
joshaber 2cc8a7d4ab Go with the swift2 branch of RAC. 2015-10-22 17:07:23 -04:00
joshaber 0026cef7bf Update all our references to RAC files. 2015-10-22 17:03:34 -04:00
joshaber f0fee4a4e8 ++RAC submodule 2015-10-22 16:41:45 -04:00
joshaber 1898bbde62 ++more things 2015-10-22 16:40:20 -04:00
joshaber 031a7cd850 ++everything 2015-10-22 16:31:59 -04:00
joshaber 27f7777ba5 Hi Xcode 7. 2015-10-22 16:17:57 -04:00
Matt Diephouse 8caa2fa200 Merge pull request #152 from Squirrel/fix-pruning
Fix pruning
2015-09-22 10:40:54 -05:00
joshaber 2fe45e940f Just use a method. 2015-09-22 00:21:23 +01:00
joshaber e50720470c Don't replay, that's crazy.
If we replay, then we don’t ever prune after the first subscription. We
just keep replaying the previous results.

Fixes https://github.com/atom/atom/issues/7061.
2015-09-21 00:17:11 +01:00
joshaber cebf4a85d6 Failing test. 2015-09-21 00:14:25 +01:00
joshaber 5f96d1e565 Allow tests to specify the number of update requests. 2015-09-21 00:14:08 +01:00
joshaber 1f3e0ccbe5 Verify that we have updates. 2015-09-21 00:10:30 +01:00
Keith Duncan 6252f3dbda Merge pull request #149 from Squirrel/renames
Renames
2015-06-29 17:09:59 +10:00
joshaber 1e71daa4f7 Merge branch 'renames-rebase' into renames
Conflicts:
	Squirrel/SQRLInstaller.m
2015-06-23 15:32:15 -04:00
joshaber 8eb2517d6b Do the rename first. 2015-06-23 15:30:58 -04:00
joshaber 46e044726f Do the rename first. 2015-06-23 14:10:39 -04:00
joshaber b30ee65933 Be more explicit. 2015-06-22 11:41:32 -04:00
joshaber ae89c0a1f6 Be more explicit. 2015-06-22 11:24:24 -04:00
joshaber 54f765b19e Naming and docs. 2015-06-19 15:30:33 -04:00
joshaber d8a1185c6b Let the abort use the old request. 2015-06-19 15:30:27 -04:00
joshaber dbf430683f Fix the tests. 2015-06-19 15:30:21 -04:00
joshaber d0634fc8e6 Naming and docs. 2015-06-19 13:48:31 -04:00
joshaber 178648f373 Let the abort use the old request. 2015-06-19 12:47:03 -04:00
joshaber 3a4428cfb3 Fix the tests. 2015-06-19 12:35:28 -04:00
joshaber dab5bf2892 Merge branch 'renames-rebase' into renames
Conflicts:
	Squirrel/SQRLInstaller.m
2015-06-19 12:31:46 -04:00
joshaber e3aa7a6767 Only rename if needed. 2015-06-19 12:21:42 -04:00
joshaber 0c8394f6f4 Rename after moving. 2015-06-19 12:08:55 -04:00
joshaber 6779322eea Deliver an updated request. 2015-06-19 11:00:07 -04:00
joshaber 5d8ffc9070 Use -installItemToURL:fromURL:. 2015-06-18 16:23:51 -04:00
joshaber 72345d4c18 Serialize whether we're allowed to rename. 2015-06-18 15:58:55 -04:00
joshaber 52a6dc2fd2 More logging. 2015-06-18 15:28:13 -04:00
joshaber 5b05c23877 Only do the dance when installing. 2015-06-18 15:18:47 -04:00
joshaber 81c0841201 Logging. 2015-06-18 14:33:09 -04:00
joshaber 747e183b98 Check the bundle ID at our new destination. 2015-06-18 14:10:03 -04:00
joshaber 62a39671fc Early exits. 2015-06-18 14:09:57 -04:00
joshaber 3be9c89f49 Use the source's name as the new name. 2015-06-18 14:09:52 -04:00
joshaber ceb4f34827 RAC em up. 2015-06-18 14:09:46 -04:00
joshaber 22e785a637 Try the dumb guy route first. 2015-06-18 14:09:41 -04:00
joshaber 221519e3ad Check the bundle ID at our new destination. 2015-06-18 13:58:38 -04:00
joshaber 7faed9ec18 Early exits. 2015-06-18 13:58:29 -04:00
joshaber c74ecf9c07 Use the source's name as the new name. 2015-06-18 13:47:47 -04:00
joshaber 04233f8f7c RAC em up. 2015-06-18 13:34:27 -04:00
joshaber 378e3891bf Try the dumb guy route first. 2015-06-18 12:27:59 -04:00
Justin Spahr-Summers 9e040347f5 Merge pull request #145 from Squirrel/xcode6.3
Upgrade dependencies for Xcode 6.3
2015-04-09 16:06:29 -07:00
Justin Spahr-Summers c99ce7dfbb Upgrade dependencies for Xcode 6.3 2015-04-09 14:59:58 -07:00
Justin Spahr-Summers 6b017d5d3f Fix call to superclass method that doesn't exist
Not sure how this ever worked.
2015-04-09 14:56:04 -07:00
Justin Spahr-Summers b1f4ecf615 Merge pull request #143 from Squirrel/quote-paths-when-signing
Quote the paths passed to otool.
2015-02-12 12:15:42 -08:00
Rob Rix fd29bc392f Quote the paths passed to otool.
We pass in a path which could contain spaces, so we need to quote it.
2015-02-12 10:02:32 -05:00
Justin Spahr-Summers 61b5ceb1cb Merge pull request #142 from Squirrel/pacify-xcode
Pacify Xcode
2015-01-16 13:50:10 -08:00
joshaber 38a8914b55 OK Computer 2015-01-16 15:06:06 -05:00
joshaber ede35f29d8 ++xcconfigs 2015-01-16 15:05:50 -05:00
Justin Spahr-Summers 7e8abd13c4 Merge pull request #141 from Squirrel/keithduncan-patch-1
Separate the update available and install instructions
2015-01-15 12:21:37 -08:00
Keith Duncan d776817d8d More tweaks 2015-01-15 12:18:34 -08:00
Keith Duncan 82d79b65c3 Tweak wording 2015-01-15 11:40:26 -08:00
Keith Duncan 8cf49c6fef Separate the update available and install instructions 2015-01-14 14:32:40 -08:00
Justin Spahr-Summers caba5b81af Merge pull request #140 from Squirrel/sign-shipit
Let Xcode code sign ShipIt.
2015-01-10 10:01:34 -08:00
joshaber 98efd568df Let Xcode code sign ShipIt. 2015-01-09 17:58:31 -08:00
155 changed files with 8126 additions and 948 deletions
+10
View File
@@ -0,0 +1,10 @@
osx_image: xcode9.4
language: objective-c
install: script/bootstrap
script: script/cibuild
notifications:
email: false
env:
global:
secure: L4C3hHzXc/sgL35dIz3kjqIzPWnXCHsFOgbOcz0f249zlnegeKHQdChm4HUDtfrbmKLMYQCIvVfcgPeLaHkCSeHqXUihXLT2LglK+0GX3MBbFzde9x1uln2O68hH1tTmBg2O2i4Dtc4D3e+2tVv3XmhQDZt0znIVO6n9YgevDqw=
+3 -3
View File
@@ -1,3 +1,3 @@
github "jspahrsummers/xcconfigs" >= 0.7
github "Quick/Quick" ~> 0.2
github "Quick/Nimble" ~> 0.2
github "jspahrsummers/xcconfigs" ~> 0.8.1
github "Quick/Quick" ~> 1.1.0
github "Quick/Nimble" ~> 6.1.0
+5 -5
View File
@@ -1,5 +1,5 @@
github "Mantle/Mantle" "1.5.3"
github "Quick/Nimble" "v0.2.0"
github "Quick/Quick" "v0.2.2"
github "ReactiveCocoa/ReactiveCocoa" "v2.4.3"
github "jspahrsummers/xcconfigs" "0.7"
github "Mantle/Mantle" "1.5.8"
github "Quick/Nimble" "v6.1.0"
github "Quick/Quick" "v1.1.0"
github "ReactiveCocoa/ReactiveCocoa" "v2.5"
github "jspahrsummers/xcconfigs" "0.8.1"
+52 -11
View File
@@ -6,6 +6,7 @@ and transparent as updates to a website**.
Instead of publishing a feed of versions from which your app must select,
Squirrel updates to the version your server tells it to. This allows you to
intelligently update your clients based on the request you give to Squirrel.
The server can remotely drive behaviors like rolling back or phased rollouts.
Your request can include authentication details, custom headers or a request
body so that your server has the context it needs in order to supply the most
@@ -46,7 +47,7 @@ version as Squirrel.
Otherwise, add a target dependency and Copy Files build phase entry for the
ReactiveCocoa.framework target included in Squirrel's repository, in
External/ReactiveCocoa.
`Carthage/Checkouts/ReactiveCocoa`.
Similarly, ensure your application includes Mantle, or copies in the Squirrel
version.
@@ -65,7 +66,7 @@ Once Squirrel is added to your project, you need to configure and start it.
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
NSURLComponents *components = [[NSURLComponents alloc] init];
components.scheme = @"http";
components.scheme = @"https";
components.host = @"mycompany.com";
components.path = @"/myapp/latest";
@@ -113,12 +114,18 @@ To know when an update is ready to be installed, you can subscribe to the
}];
```
If you've been notified of an available update, and don't want to wait for it to
be installed automatically, you can terminate the app to begin the installation
process immediately.
## Installing Updates
If you want to install a downloaded update and automatically relaunch afterward,
`SQRLUpdater` can do that:
While downloaded updates are automatically installed when your application
terminates, if don't want to wait you can manually terminate the app to begin
the installation process immediately.
Once an [update available notification](#update-available-notifications) has
been received, you may want to present an interface informing the user about
the update and offering the ability to install and relaunch.
To explicitly install a downloaded update and automatically relaunch afterward,
subscribe to the `relaunchToInstallUpdate` signal on `SQRLUpdater`:
```objc
[[self.updater relaunchToInstallUpdate] subscribeError:^(NSError *error) {
@@ -133,7 +140,7 @@ Your server should determine whether an update is required based on the
If an update is required your server should respond with a status code of
[200 OK](http://tools.ietf.org/html/rfc2616#section-10.2.1) and include the
[update JSON](#update-json-format) in the body. Squirrel **will** download and
[update JSON](#update-server-json-format) in the body. Squirrel **will** download and
install this update, even if the version of the update is the same as the
currently running version. To save redundantly downloading the same version
multiple times your server must not inform the client to update.
@@ -142,17 +149,17 @@ If no update is required your server must respond with a status code of
[204 No Content](http://tools.ietf.org/html/rfc2616#section-10.2.5). Squirrel
will check for an update again at the interval you specify.
## Update JSON Format
## Update Server JSON Format
When an update is available, Squirrel expects the following schema in response
to the update request provided:
```json
{
"url": "http://mycompany.com/myapp/releases/myrelease",
"url": "https://mycompany.example.com/myapp/releases/myrelease",
"name": "My Release Name",
"notes": "Theses are some release notes innit",
"pub_date": "2013-09-18T12:29:53+01:00",
"pub_date": "2013-09-18T12:29:53+01:00"
}
```
@@ -165,6 +172,40 @@ appropriate format.
"pub_date" if present must be formatted according to ISO 8601.
## Update File JSON Format
The alternate update technique uses a plain JSON file meaning you can store your
update metadata on S3 or another static file store. The format of this file is
detailed below:
```json
{
"currentRelease": "1.2.3",
"releases": [
{
"version": "1.2.1",
"updateTo": {
"version": "1.2.1",
"pub_date": "2013-09-18T12:29:53+01:00",
"notes": "Theses are some release notes innit",
"name": "1.2.1",
"url": "https://mycompany.example.com/myapp/releases/myrelease"
}
},
{
"version": "1.2.3",
"updateTo": {
"version": "1.2.3",
"pub_date": "2014-09-18T12:29:53+01:00",
"notes": "Theses are some more release notes innit",
"name": "1.2.3",
"url": "https://mycompany.example.com/myapp/releases/myrelease3"
}
}
]
}
```
# User Interface
Squirrel does not provide any GUI components for presenting updates. If you want
+94 -34
View File
@@ -8,13 +8,11 @@
/* Begin PBXBuildFile section */
533076EB17EB688300BDCCE0 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D097B9BC17BB4777006C3FEB /* IOKit.framework */; };
534FF35B17D8E8B90020A51A /* unused-helper in Copy LoginItems */ = {isa = PBXBuildFile; fileRef = D0B1DDCB17B487D90059C355 /* unused-helper */; };
534FF35B17D8E8B90020A51A /* unused-helper in Copy LoginItems */ = {isa = PBXBuildFile; fileRef = D0B1DDCB17B487D90059C355 /* unused-helper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
534FF36117D8E90A0020A51A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F60CA7EA179FC4F60069F69A /* Foundation.framework */; };
534FF36717D8E90A0020A51A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 534FF36517D8E90A0020A51A /* InfoPlist.strings */; };
534FF36A17D8E90A0020A51A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 534FF36917D8E90A0020A51A /* main.m */; };
534FF37317D8E9370020A51A /* com.github.Squirrel.TestApplication.TestService.xpc in Copy XPCServices */ = {isa = PBXBuildFile; fileRef = 534FF36017D8E90A0020A51A /* com.github.Squirrel.TestApplication.TestService.xpc */; };
53710D7417D8F59700A992DE /* SQRLDeepCodesignSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 53710D7317D8F59700A992DE /* SQRLDeepCodesignSpec.m */; };
53710D8417D8F5CB00A992DE /* deep-codesign in Resources */ = {isa = PBXBuildFile; fileRef = 53710D7F17D8F5C300A992DE /* deep-codesign */; };
534FF37317D8E9370020A51A /* com.github.Squirrel.TestApplication.TestService.xpc in Copy XPCServices */ = {isa = PBXBuildFile; fileRef = 534FF36017D8E90A0020A51A /* com.github.Squirrel.TestApplication.TestService.xpc */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
5371815F18A29DC8005ED798 /* TestAppConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 5371815E18A29DC8005ED798 /* TestAppConstants.m */; };
5371816018A29DC8005ED798 /* TestAppConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 5371815E18A29DC8005ED798 /* TestAppConstants.m */; };
5374DCC6187AD0D8006B7056 /* SQRLAuthorization.h in Headers */ = {isa = PBXBuildFile; fileRef = 5374DCC4187AD0D8006B7056 /* SQRLAuthorization.h */; };
@@ -27,6 +25,8 @@
5397A69D187DF8610014A477 /* SQRLInstallerOwnedBundle.m in Sources */ = {isa = PBXBuildFile; fileRef = 5397A5EB187DA2570014A477 /* SQRLInstallerOwnedBundle.m */; };
53AACF4B17E9CA0500B41027 /* SQRLUpdate.h in Headers */ = {isa = PBXBuildFile; fileRef = 53AACF4917E9CA0500B41027 /* SQRLUpdate.h */; settings = {ATTRIBUTES = (Public, ); }; };
53AACF4C17E9CA0500B41027 /* SQRLUpdate.m in Sources */ = {isa = PBXBuildFile; fileRef = 53AACF4A17E9CA0500B41027 /* SQRLUpdate.m */; };
88CA21971BD980FC006693B7 /* MTLEXTRuntimeExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 88CA21941BD980FC006693B7 /* MTLEXTRuntimeExtensions.m */; };
88CA21981BD980FC006693B7 /* MTLEXTScope.m in Sources */ = {isa = PBXBuildFile; fileRef = 88CA21961BD980FC006693B7 /* MTLEXTScope.m */; };
D000218F17BACEFF0050109A /* SQRLZipArchiver.m in Sources */ = {isa = PBXBuildFile; fileRef = D000218D17BACEFF0050109A /* SQRLZipArchiver.m */; };
D000219717BAD34D0050109A /* TestApplication.app.zip in Resources */ = {isa = PBXBuildFile; fileRef = D000219617BAD34D0050109A /* TestApplication.app.zip */; };
D000219917BAD35C0050109A /* SQRLZipArchiverSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D000219817BAD35C0050109A /* SQRLZipArchiverSpec.m */; };
@@ -37,7 +37,7 @@
D014ABBA17B97403007D79D0 /* ServiceManagement.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D014ABB917B97403007D79D0 /* ServiceManagement.framework */; };
D014AC0517B97885007D79D0 /* ShipIt-main.m in Sources */ = {isa = PBXBuildFile; fileRef = D014AC0417B97885007D79D0 /* ShipIt-main.m */; };
D014AC1117B9789C007D79D0 /* SQRLInstaller.m in Sources */ = {isa = PBXBuildFile; fileRef = D014AC1017B9789C007D79D0 /* SQRLInstaller.m */; };
D014AC1817B97945007D79D0 /* ShipIt in Copy ShipIt */ = {isa = PBXBuildFile; fileRef = D014AC0117B97885007D79D0 /* ShipIt */; };
D014AC1817B97945007D79D0 /* ShipIt in Copy ShipIt */ = {isa = PBXBuildFile; fileRef = D014AC0117B97885007D79D0 /* ShipIt */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
D014AC1917B979A8007D79D0 /* SQRLCodeSignature.m in Sources */ = {isa = PBXBuildFile; fileRef = F6F2ED8217A33C4900F4D220 /* SQRLCodeSignature.m */; };
D014AC1A17B979AA007D79D0 /* NSError+SQRLVerbosityExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = D0EDBE9B17B0C68E0058BC3C /* NSError+SQRLVerbosityExtensions.m */; };
D014AC1D17B979B7007D79D0 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6EB217F179D0E4D001108CF /* Security.framework */; };
@@ -55,6 +55,7 @@
D06B58B518032B1500656D97 /* RACSignal+SQRLTransactionExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = D06B58B318032B1500656D97 /* RACSignal+SQRLTransactionExtensions.m */; };
D06B58B618032B1500656D97 /* RACSignal+SQRLTransactionExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = D06B58B318032B1500656D97 /* RACSignal+SQRLTransactionExtensions.m */; };
D06B58D118035B5A00656D97 /* SQRLTerminationListenerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D06B58D018035B5A00656D97 /* SQRLTerminationListenerSpec.m */; };
D06F7B101AD72DD30009A3BC /* RACKVOProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = D06F7B0F1AD72DD30009A3BC /* RACKVOProxy.m */; };
D08D4E0D17B451500012B22D /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0C22BDA179CC00E00158214 /* Cocoa.framework */; };
D08D4E1317B451500012B22D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D08D4E1117B451500012B22D /* InfoPlist.strings */; };
D08D4E1517B451500012B22D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = D08D4E1417B451500012B22D /* main.m */; };
@@ -62,7 +63,7 @@
D08D4E1C17B451500012B22D /* TestAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D08D4E1B17B451500012B22D /* TestAppDelegate.m */; };
D08D4E2117B451500012B22D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D08D4E2017B451500012B22D /* Images.xcassets */; };
D08D4E4717B451E50012B22D /* Squirrel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0C22BD7179CC00E00158214 /* Squirrel.framework */; };
D08D4E4917B451F20012B22D /* Squirrel.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D0C22BD7179CC00E00158214 /* Squirrel.framework */; };
D08D4E4917B451F20012B22D /* Squirrel.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D0C22BD7179CC00E00158214 /* Squirrel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
D08D4E4A17B451FD0012B22D /* TestApplication.app in Resources */ = {isa = PBXBuildFile; fileRef = D08D4E0C17B451500012B22D /* TestApplication.app */; };
D093682417FF732200BA0EAE /* ServiceManagement.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D014ABB917B97403007D79D0 /* ServiceManagement.framework */; };
D0964B3817F2E01500D88BF7 /* SQRLDownloadedUpdate.h in Headers */ = {isa = PBXBuildFile; fileRef = D0964B3617F2E01500D88BF7 /* SQRLDownloadedUpdate.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -74,7 +75,6 @@
D09C66291A00520B007D7ED0 /* NSArray+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D09C66281A00520B007D7ED0 /* NSArray+RACSequenceAdditions.m */; };
D09C662C1A005226007D7ED0 /* NSObject+RACKVOWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = D09C662A1A005226007D7ED0 /* NSObject+RACKVOWrapper.m */; };
D09C662D1A005226007D7ED0 /* NSObject+RACPropertySubscribing.m in Sources */ = {isa = PBXBuildFile; fileRef = D09C662B1A005226007D7ED0 /* NSObject+RACPropertySubscribing.m */; };
D09C66331A0052DE007D7ED0 /* EXTRuntimeExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = D09C66321A0052DE007D7ED0 /* EXTRuntimeExtensions.m */; };
D09C66351A00535B007D7ED0 /* RACKVOTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = D09C66341A00535B007D7ED0 /* RACKVOTrampoline.m */; };
D09C66381A00537A007D7ED0 /* NSObject+RACDeallocating.m in Sources */ = {isa = PBXBuildFile; fileRef = D09C66361A00537A007D7ED0 /* NSObject+RACDeallocating.m */; };
D09C66391A00537A007D7ED0 /* NSObject+RACDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = D09C66371A00537A007D7ED0 /* NSObject+RACDescription.m */; };
@@ -135,7 +135,7 @@
D0AFE3901A00294100C6048F /* SwiftSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AFE38F1A00294100C6048F /* SwiftSpec.swift */; };
D0AFE3931A00297500C6048F /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0AFE3921A00297500C6048F /* Quick.framework */; };
D0AFE3951A0029A700C6048F /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0AFE3941A0029A700C6048F /* Nimble.framework */; };
D0B1DDCE17B487FB0059C355 /* unused-helper in Copy Executables */ = {isa = PBXBuildFile; fileRef = D0B1DDCB17B487D90059C355 /* unused-helper */; };
D0B1DDCE17B487FB0059C355 /* unused-helper in Copy Executables */ = {isa = PBXBuildFile; fileRef = D0B1DDCB17B487D90059C355 /* unused-helper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
D0C22BDB179CC00E00158214 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0C22BDA179CC00E00158214 /* Cocoa.framework */; };
D0C22BF2179CC00E00158214 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0C22BDA179CC00E00158214 /* Cocoa.framework */; };
D0C22BF5179CC00E00158214 /* Squirrel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0C22BD7179CC00E00158214 /* Squirrel.framework */; };
@@ -145,7 +145,6 @@
D0C22C52179CC23900158214 /* Squirrel.h in Headers */ = {isa = PBXBuildFile; fileRef = D0C22BE7179CC00E00158214 /* Squirrel.h */; settings = {ATTRIBUTES = (Public, ); }; };
D0D2B6261804E903000EA901 /* SQRLDirectoryManager.m in Sources */ = {isa = PBXBuildFile; fileRef = D0D2B6241804E903000EA901 /* SQRLDirectoryManager.m */; };
D0D2B6271804E903000EA901 /* SQRLDirectoryManager.m in Sources */ = {isa = PBXBuildFile; fileRef = D0D2B6241804E903000EA901 /* SQRLDirectoryManager.m */; };
D0D97E4517B58FC400301C72 /* ResourceRules.plist in Resources */ = {isa = PBXBuildFile; fileRef = D0D97E4417B58FC400301C72 /* ResourceRules.plist */; };
D0EDBE9D17B0C68E0058BC3C /* NSError+SQRLVerbosityExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = D0EDBE9B17B0C68E0058BC3C /* NSError+SQRLVerbosityExtensions.m */; };
D0EDBEDD17B0DB3E0058BC3C /* QuickSpec+SQRLFixtures.m in Sources */ = {isa = PBXBuildFile; fileRef = D0EDBEDC17B0DB3E0058BC3C /* QuickSpec+SQRLFixtures.m */; };
D0EDBEE117B0E3650058BC3C /* SQRLCodeSignatureSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D0EDBEE017B0E3650058BC3C /* SQRLCodeSignatureSpec.m */; };
@@ -156,8 +155,8 @@
D4ACA50118DB9F3F00EBD899 /* OHHTTPStubs.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D4ACA50018DB9F3F00EBD899 /* OHHTTPStubs.framework */; };
D4C4909918E46DE900786EFE /* Mantle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D4C4909818E46DE900786EFE /* Mantle.framework */; };
D4C4909B18E46DFE00786EFE /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D4C4909A18E46DFE00786EFE /* ReactiveCocoa.framework */; };
D4C4909E18E46E1000786EFE /* ReactiveCocoa.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D4C4909A18E46DFE00786EFE /* ReactiveCocoa.framework */; };
D4C4909F18E46E1000786EFE /* Mantle.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D4C4909818E46DE900786EFE /* Mantle.framework */; };
D4C4909E18E46E1000786EFE /* ReactiveCocoa.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D4C4909A18E46DFE00786EFE /* ReactiveCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
D4C4909F18E46E1000786EFE /* Mantle.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D4C4909818E46DE900786EFE /* Mantle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
F6EB2162179CFD93001108CF /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6EB2161179CFD93001108CF /* SystemConfiguration.framework */; };
F6EB2180179D0E4D001108CF /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6EB217F179D0E4D001108CF /* Security.framework */; };
F6F2ED8417A33C4900F4D220 /* SQRLCodeSignature.m in Sources */ = {isa = PBXBuildFile; fileRef = F6F2ED8217A33C4900F4D220 /* SQRLCodeSignature.m */; };
@@ -267,10 +266,8 @@
534FF36617D8E90A0020A51A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
534FF36817D8E90A0020A51A /* TestService-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TestService-Prefix.pch"; sourceTree = "<group>"; };
534FF36917D8E90A0020A51A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
53710D7317D8F59700A992DE /* SQRLDeepCodesignSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SQRLDeepCodesignSpec.m; sourceTree = "<group>"; };
53710D7D17D8F5C300A992DE /* bootstrap */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = bootstrap; sourceTree = "<group>"; };
53710D7E17D8F5C300A992DE /* cibuild */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = cibuild; sourceTree = "<group>"; };
53710D7F17D8F5C300A992DE /* deep-codesign */ = {isa = PBXFileReference; lastKnownFileType = text; path = "deep-codesign"; sourceTree = "<group>"; };
53710D8017D8F5C300A992DE /* LICENSE.md */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE.md; sourceTree = "<group>"; };
53710D8117D8F5C300A992DE /* README.md */ = {isa = PBXFileReference; lastKnownFileType = text; path = README.md; sourceTree = "<group>"; };
53710D8217D8F5C300A992DE /* targets.awk */ = {isa = PBXFileReference; lastKnownFileType = text; path = targets.awk; sourceTree = "<group>"; };
@@ -287,6 +284,12 @@
5397A69C187DF6490014A477 /* SQRLInstaller+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SQRLInstaller+Private.h"; sourceTree = "<group>"; };
53AACF4917E9CA0500B41027 /* SQRLUpdate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SQRLUpdate.h; sourceTree = "<group>"; };
53AACF4A17E9CA0500B41027 /* SQRLUpdate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SQRLUpdate.m; sourceTree = "<group>"; };
88CA21911BD980FC006693B7 /* metamacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = metamacros.h; path = Carthage/Checkouts/Mantle/Mantle/extobjc/metamacros.h; sourceTree = SOURCE_ROOT; };
88CA21921BD980FC006693B7 /* MTLEXTKeyPathCoding.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTLEXTKeyPathCoding.h; path = Carthage/Checkouts/Mantle/Mantle/extobjc/MTLEXTKeyPathCoding.h; sourceTree = SOURCE_ROOT; };
88CA21931BD980FC006693B7 /* MTLEXTRuntimeExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTLEXTRuntimeExtensions.h; path = Carthage/Checkouts/Mantle/Mantle/extobjc/MTLEXTRuntimeExtensions.h; sourceTree = SOURCE_ROOT; };
88CA21941BD980FC006693B7 /* MTLEXTRuntimeExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTLEXTRuntimeExtensions.m; path = Carthage/Checkouts/Mantle/Mantle/extobjc/MTLEXTRuntimeExtensions.m; sourceTree = SOURCE_ROOT; };
88CA21951BD980FC006693B7 /* MTLEXTScope.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTLEXTScope.h; path = Carthage/Checkouts/Mantle/Mantle/extobjc/MTLEXTScope.h; sourceTree = SOURCE_ROOT; };
88CA21961BD980FC006693B7 /* MTLEXTScope.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTLEXTScope.m; path = Carthage/Checkouts/Mantle/Mantle/extobjc/MTLEXTScope.m; sourceTree = SOURCE_ROOT; };
D000218C17BACEFF0050109A /* SQRLZipArchiver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SQRLZipArchiver.h; sourceTree = "<group>"; };
D000218D17BACEFF0050109A /* SQRLZipArchiver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SQRLZipArchiver.m; sourceTree = "<group>"; };
D000219617BAD34D0050109A /* TestApplication.app.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = TestApplication.app.zip; sourceTree = "<group>"; };
@@ -314,6 +317,7 @@
D06B58B218032B1500656D97 /* RACSignal+SQRLTransactionExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RACSignal+SQRLTransactionExtensions.h"; sourceTree = "<group>"; };
D06B58B318032B1500656D97 /* RACSignal+SQRLTransactionExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RACSignal+SQRLTransactionExtensions.m"; sourceTree = "<group>"; };
D06B58D018035B5A00656D97 /* SQRLTerminationListenerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SQRLTerminationListenerSpec.m; sourceTree = "<group>"; };
D06F7B0F1AD72DD30009A3BC /* RACKVOProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RACKVOProxy.m; path = Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/RACKVOProxy.m; sourceTree = SOURCE_ROOT; };
D08D4E0C17B451500012B22D /* TestApplication.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestApplication.app; sourceTree = BUILT_PRODUCTS_DIR; };
D08D4E1017B451500012B22D /* TestApplication-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "TestApplication-Info.plist"; sourceTree = "<group>"; };
D08D4E1217B451500012B22D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
@@ -331,8 +335,6 @@
D09C66281A00520B007D7ED0 /* NSArray+RACSequenceAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSArray+RACSequenceAdditions.m"; path = "Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/NSArray+RACSequenceAdditions.m"; sourceTree = SOURCE_ROOT; };
D09C662A1A005226007D7ED0 /* NSObject+RACKVOWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSObject+RACKVOWrapper.m"; path = "Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/NSObject+RACKVOWrapper.m"; sourceTree = SOURCE_ROOT; };
D09C662B1A005226007D7ED0 /* NSObject+RACPropertySubscribing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSObject+RACPropertySubscribing.m"; path = "Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/NSObject+RACPropertySubscribing.m"; sourceTree = SOURCE_ROOT; };
D09C66311A0052DE007D7ED0 /* EXTRuntimeExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = EXTRuntimeExtensions.h; path = Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/extobjc/EXTRuntimeExtensions.h; sourceTree = SOURCE_ROOT; };
D09C66321A0052DE007D7ED0 /* EXTRuntimeExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = EXTRuntimeExtensions.m; path = Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/extobjc/EXTRuntimeExtensions.m; sourceTree = SOURCE_ROOT; };
D09C66341A00535B007D7ED0 /* RACKVOTrampoline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RACKVOTrampoline.m; path = Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/RACKVOTrampoline.m; sourceTree = SOURCE_ROOT; };
D09C66361A00537A007D7ED0 /* NSObject+RACDeallocating.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSObject+RACDeallocating.m"; path = "Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/NSObject+RACDeallocating.m"; sourceTree = SOURCE_ROOT; };
D09C66371A00537A007D7ED0 /* NSObject+RACDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSObject+RACDescription.m"; path = "Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/NSObject+RACDescription.m"; sourceTree = SOURCE_ROOT; };
@@ -422,7 +424,6 @@
D0C22C4F179CC18C00158214 /* SQRLUpdaterSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SQRLUpdaterSpec.m; sourceTree = "<group>"; };
D0D2B6231804E903000EA901 /* SQRLDirectoryManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SQRLDirectoryManager.h; sourceTree = "<group>"; };
D0D2B6241804E903000EA901 /* SQRLDirectoryManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SQRLDirectoryManager.m; sourceTree = "<group>"; };
D0D97E4417B58FC400301C72 /* ResourceRules.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = ResourceRules.plist; sourceTree = "<group>"; };
D0EDBE9A17B0C68E0058BC3C /* NSError+SQRLVerbosityExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSError+SQRLVerbosityExtensions.h"; path = "Squirrel/NSError+SQRLVerbosityExtensions.h"; sourceTree = SOURCE_ROOT; };
D0EDBE9B17B0C68E0058BC3C /* NSError+SQRLVerbosityExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSError+SQRLVerbosityExtensions.m"; path = "Squirrel/NSError+SQRLVerbosityExtensions.m"; sourceTree = SOURCE_ROOT; };
D0EDBEDB17B0DB3E0058BC3C /* QuickSpec+SQRLFixtures.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "QuickSpec+SQRLFixtures.h"; sourceTree = "<group>"; };
@@ -529,7 +530,6 @@
children = (
53710D7D17D8F5C300A992DE /* bootstrap */,
53710D7E17D8F5C300A992DE /* cibuild */,
53710D7F17D8F5C300A992DE /* deep-codesign */,
53710D8017D8F5C300A992DE /* LICENSE.md */,
53710D8117D8F5C300A992DE /* README.md */,
53710D8217D8F5C300A992DE /* targets.awk */,
@@ -621,7 +621,6 @@
D08D4E0F17B451500012B22D /* Supporting Files */ = {
isa = PBXGroup;
children = (
D0D97E4417B58FC400301C72 /* ResourceRules.plist */,
D0B1DDCB17B487D90059C355 /* unused-helper */,
D08D4E1017B451500012B22D /* TestApplication-Info.plist */,
D08D4E1117B451500012B22D /* InfoPlist.strings */,
@@ -687,8 +686,12 @@
D0AFE3371A00282000C6048F /* Static Dependencies */ = {
isa = PBXGroup;
children = (
D09C66311A0052DE007D7ED0 /* EXTRuntimeExtensions.h */,
D09C66321A0052DE007D7ED0 /* EXTRuntimeExtensions.m */,
88CA21911BD980FC006693B7 /* metamacros.h */,
88CA21921BD980FC006693B7 /* MTLEXTKeyPathCoding.h */,
88CA21931BD980FC006693B7 /* MTLEXTRuntimeExtensions.h */,
88CA21941BD980FC006693B7 /* MTLEXTRuntimeExtensions.m */,
88CA21951BD980FC006693B7 /* MTLEXTScope.h */,
88CA21961BD980FC006693B7 /* MTLEXTScope.m */,
D0AFE3381A00282C00C6048F /* MTLJSONAdapter.m */,
D0AFE3391A00282C00C6048F /* MTLModel.m */,
D09C66401A005592007D7ED0 /* MTLModel+NSCoding.m */,
@@ -722,6 +725,7 @@
D0AFE3551A0028A400C6048F /* RACEvent.m */,
D0AFE3561A0028A400C6048F /* RACGroupedSignal.m */,
D0AFE3571A0028A400C6048F /* RACImmediateScheduler.m */,
D06F7B0F1AD72DD30009A3BC /* RACKVOProxy.m */,
D09C66341A00535B007D7ED0 /* RACKVOTrampoline.m */,
D0AFE3581A0028A400C6048F /* RACMulticastConnection.m */,
D09C663A1A00538D007D7ED0 /* RACObjCRuntime.m */,
@@ -922,7 +926,6 @@
children = (
D00F5B8D17E82DE6009A4818 /* NSProcessInfoExtensionsSpec.m */,
D0EDBEE017B0E3650058BC3C /* SQRLCodeSignatureSpec.m */,
53710D7317D8F59700A992DE /* SQRLDeepCodesignSpec.m */,
D049059A180554BA004E683E /* SQRLDirectoryManagerSpec.m */,
D09D244017B595470001FAF8 /* SQRLInstallerSpec.m */,
D049059C18055671004E683E /* SQRLShipItRequestSpec.m */,
@@ -1070,8 +1073,18 @@
isa = PBXProject;
attributes = {
CLASSPREFIX = SQRL;
LastUpgradeCheck = 0510;
LastSwiftUpdateCheck = 0710;
LastUpgradeCheck = 0830;
ORGANIZATIONNAME = GitHub;
TargetAttributes = {
D08D4E0B17B451500012B22D = {
DevelopmentTeam = C4RRYD56A6;
ProvisioningStyle = Automatic;
};
D0C22BEE179CC00E00158214 = {
LastSwiftMigration = 0830;
};
};
};
buildConfigurationList = D0C22BD1179CC00E00158214 /* Build configuration list for PBXProject "Squirrel" */;
compatibilityVersion = "Xcode 3.2";
@@ -1111,7 +1124,6 @@
D08D4E1317B451500012B22D /* InfoPlist.strings in Resources */,
D08D4E2117B451500012B22D /* Images.xcassets in Resources */,
D08D4E1917B451500012B22D /* Credits.rtf in Resources */,
D0D97E4517B58FC400301C72 /* ResourceRules.plist in Resources */,
D03BE98D17B6174500458593 /* MainMenu.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1130,7 +1142,6 @@
D08D4E4A17B451FD0012B22D /* TestApplication.app in Resources */,
D09D244A17B59AB30001FAF8 /* TestApplication 2.1.app in Resources */,
D000219717BAD34D0050109A /* TestApplication.app.zip in Resources */,
53710D8417D8F5CB00A992DE /* deep-codesign in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1164,6 +1175,7 @@
D0AFE37C1A0028B000C6048F /* RACCompoundDisposableProvider.d in Sources */,
D0AFE3441A00285000C6048F /* NSValueTransformer+MTLPredefinedTransformerAdditions.m in Sources */,
D0AFE38B1A0028EC00C6048F /* RACArraySequence.m in Sources */,
D06F7B101AD72DD30009A3BC /* RACKVOProxy.m in Sources */,
D0AFE3751A0028A400C6048F /* RACSubject.m in Sources */,
D014AC0517B97885007D79D0 /* ShipIt-main.m in Sources */,
D0AFE3431A00285000C6048F /* MTLReflection.m in Sources */,
@@ -1194,11 +1206,11 @@
D0AFE38E1A0028EC00C6048F /* RACUnarySequence.m in Sources */,
D0AFE33B1A00282C00C6048F /* MTLJSONAdapter.m in Sources */,
D09C66351A00535B007D7ED0 /* RACKVOTrampoline.m in Sources */,
88CA21981BD980FC006693B7 /* MTLEXTScope.m in Sources */,
D0AFE3691A0028A400C6048F /* RACEmptySignal.m in Sources */,
D09C66391A00537A007D7ED0 /* NSObject+RACDescription.m in Sources */,
D0D2B6271804E903000EA901 /* SQRLDirectoryManager.m in Sources */,
D0AFE36D1A0028A400C6048F /* RACImmediateScheduler.m in Sources */,
D09C66331A0052DE007D7ED0 /* EXTRuntimeExtensions.m in Sources */,
D0AFE36A1A0028A400C6048F /* RACErrorSignal.m in Sources */,
D0AFE3791A0028A400C6048F /* RACTuple.m in Sources */,
D0AFE3811A0028CC00C6048F /* RACPassthroughSubscriber.m in Sources */,
@@ -1207,6 +1219,7 @@
D0AFE36C1A0028A400C6048F /* RACGroupedSignal.m in Sources */,
D0AFE34B1A00287100C6048F /* RACDisposable.m in Sources */,
D0AFE3731A0028A400C6048F /* RACSignalSequence.m in Sources */,
88CA21971BD980FC006693B7 /* MTLEXTRuntimeExtensions.m in Sources */,
D09C662D1A005226007D7ED0 /* NSObject+RACPropertySubscribing.m in Sources */,
D0AFE3861A0028D500C6048F /* RACSignalProvider.d in Sources */,
D0AFE36B1A0028A400C6048F /* RACEvent.m in Sources */,
@@ -1265,7 +1278,6 @@
D06B58D118035B5A00656D97 /* SQRLTerminationListenerSpec.m in Sources */,
5371815F18A29DC8005ED798 /* TestAppConstants.m in Sources */,
D0EDBEE117B0E3650058BC3C /* SQRLCodeSignatureSpec.m in Sources */,
53710D7417D8F59700A992DE /* SQRLDeepCodesignSpec.m in Sources */,
D00F5B8E17E82DE6009A4818 /* NSProcessInfoExtensionsSpec.m in Sources */,
D049059B180554BA004E683E /* SQRLDirectoryManagerSpec.m in Sources */,
5397A69D187DF8610014A477 /* SQRLInstallerOwnedBundle.m in Sources */,
@@ -1340,6 +1352,7 @@
GCC_PREFIX_HEADER = "TestService/TestService-Prefix.pch";
INFOPLIST_FILE = "TestService/TestService-Info.plist";
MACH_O_TYPE = mh_execute;
PRODUCT_BUNDLE_IDENTIFIER = "${PRODUCT_NAME}";
PRODUCT_NAME = "com.github.Squirrel.TestApplication.$(TARGET_NAME:rfc1034identifier)";
WRAPPER_EXTENSION = xpc;
};
@@ -1352,6 +1365,7 @@
GCC_PREFIX_HEADER = "TestService/TestService-Prefix.pch";
INFOPLIST_FILE = "TestService/TestService-Info.plist";
MACH_O_TYPE = mh_execute;
PRODUCT_BUNDLE_IDENTIFIER = "${PRODUCT_NAME}";
PRODUCT_NAME = "com.github.Squirrel.TestApplication.$(TARGET_NAME:rfc1034identifier)";
WRAPPER_EXTENSION = xpc;
};
@@ -1364,6 +1378,7 @@
GCC_PREFIX_HEADER = "TestService/TestService-Prefix.pch";
INFOPLIST_FILE = "TestService/TestService-Info.plist";
MACH_O_TYPE = mh_execute;
PRODUCT_BUNDLE_IDENTIFIER = "${PRODUCT_NAME}";
PRODUCT_NAME = "com.github.Squirrel.TestApplication.$(TARGET_NAME:rfc1034identifier)";
WRAPPER_EXTENSION = xpc;
};
@@ -1376,6 +1391,7 @@
GCC_PREFIX_HEADER = "TestService/TestService-Prefix.pch";
INFOPLIST_FILE = "TestService/TestService-Info.plist";
MACH_O_TYPE = mh_execute;
PRODUCT_BUNDLE_IDENTIFIER = "${PRODUCT_NAME}";
PRODUCT_NAME = "com.github.Squirrel.TestApplication.$(TARGET_NAME:rfc1034identifier)";
WRAPPER_EXTENSION = xpc;
};
@@ -1462,12 +1478,14 @@
baseConfigurationReference = D0C22C17179CC02E00158214 /* Mac-Application.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_RESOURCE_RULES_PATH = TestApplication/ResourceRules.plist;
CODE_SIGN_IDENTITY = "Mac Developer";
DEVELOPMENT_TEAM = C4RRYD56A6;
GCC_PREFIX_HEADER = "TestApplication/TestApplication-Prefix.pch";
INFOPLIST_FILE = "TestApplication/TestApplication-Info.plist";
OTHER_CODE_SIGN_FLAGS = "--timestamp=none";
PRODUCT_BUNDLE_IDENTIFIER = "com.github.Squirrel.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
VALIDATE_PRODUCT = YES;
WRAPPER_EXTENSION = app;
};
@@ -1478,12 +1496,14 @@
baseConfigurationReference = D0C22C17179CC02E00158214 /* Mac-Application.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_RESOURCE_RULES_PATH = TestApplication/ResourceRules.plist;
CODE_SIGN_IDENTITY = "Mac Developer";
DEVELOPMENT_TEAM = C4RRYD56A6;
GCC_PREFIX_HEADER = "TestApplication/TestApplication-Prefix.pch";
INFOPLIST_FILE = "TestApplication/TestApplication-Info.plist";
OTHER_CODE_SIGN_FLAGS = "--timestamp=none";
PRODUCT_BUNDLE_IDENTIFIER = "com.github.Squirrel.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
VALIDATE_PRODUCT = YES;
WRAPPER_EXTENSION = app;
};
@@ -1494,12 +1514,14 @@
baseConfigurationReference = D0C22C17179CC02E00158214 /* Mac-Application.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_RESOURCE_RULES_PATH = TestApplication/ResourceRules.plist;
CODE_SIGN_IDENTITY = "Mac Developer";
DEVELOPMENT_TEAM = C4RRYD56A6;
GCC_PREFIX_HEADER = "TestApplication/TestApplication-Prefix.pch";
INFOPLIST_FILE = "TestApplication/TestApplication-Info.plist";
OTHER_CODE_SIGN_FLAGS = "--timestamp=none";
PRODUCT_BUNDLE_IDENTIFIER = "com.github.Squirrel.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
VALIDATE_PRODUCT = YES;
WRAPPER_EXTENSION = app;
};
@@ -1510,12 +1532,14 @@
baseConfigurationReference = D0C22C17179CC02E00158214 /* Mac-Application.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_RESOURCE_RULES_PATH = TestApplication/ResourceRules.plist;
CODE_SIGN_IDENTITY = "Mac Developer";
DEVELOPMENT_TEAM = C4RRYD56A6;
GCC_PREFIX_HEADER = "TestApplication/TestApplication-Prefix.pch";
INFOPLIST_FILE = "TestApplication/TestApplication-Info.plist";
OTHER_CODE_SIGN_FLAGS = "--timestamp=none";
PRODUCT_BUNDLE_IDENTIFIER = "com.github.Squirrel.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
VALIDATE_PRODUCT = YES;
WRAPPER_EXTENSION = app;
};
@@ -1525,7 +1549,12 @@
isa = XCBuildConfiguration;
baseConfigurationReference = D0C22C0B179CC02E00158214 /* Debug.xcconfig */;
buildSettings = {
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
ENABLE_TESTABILITY = YES;
GCC_NO_COMMON_BLOCKS = YES;
MACOSX_DEPLOYMENT_TARGET = 10.8;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
};
name = Debug;
@@ -1534,8 +1563,12 @@
isa = XCBuildConfiguration;
baseConfigurationReference = D0C22C0D179CC02E00158214 /* Release.xcconfig */;
buildSettings = {
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
GCC_NO_COMMON_BLOCKS = YES;
MACOSX_DEPLOYMENT_TARGET = 10.8;
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
};
name = Release;
};
@@ -1543,6 +1576,8 @@
isa = XCBuildConfiguration;
baseConfigurationReference = D0C22C1A179CC02E00158214 /* Mac-Framework.xcconfig */;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = NO;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = NO;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
FRAMEWORK_VERSION = A;
@@ -1551,6 +1586,7 @@
Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/extobjc,
);
INFOPLIST_FILE = "Squirrel/Squirrel-Info.plist";
PRODUCT_BUNDLE_IDENTIFIER = "com.github.${PRODUCT_NAME:rfc1034identifier}";
WRAPPER_EXTENSION = framework;
};
name = Debug;
@@ -1559,6 +1595,8 @@
isa = XCBuildConfiguration;
baseConfigurationReference = D0C22C1A179CC02E00158214 /* Mac-Framework.xcconfig */;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = NO;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = NO;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
FRAMEWORK_VERSION = A;
@@ -1567,6 +1605,7 @@
Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/extobjc,
);
INFOPLIST_FILE = "Squirrel/Squirrel-Info.plist";
PRODUCT_BUNDLE_IDENTIFIER = "com.github.${PRODUCT_NAME:rfc1034identifier}";
WRAPPER_EXTENSION = framework;
};
name = Release;
@@ -1585,7 +1624,9 @@
);
INFOPLIST_FILE = "SquirrelTests/SquirrelTests-Info.plist";
MACOSX_DEPLOYMENT_TARGET = 10.9;
PRODUCT_BUNDLE_IDENTIFIER = "com.github.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
};
name = Debug;
};
@@ -1603,7 +1644,9 @@
);
INFOPLIST_FILE = "SquirrelTests/SquirrelTests-Info.plist";
MACOSX_DEPLOYMENT_TARGET = 10.9;
PRODUCT_BUNDLE_IDENTIFIER = "com.github.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
};
name = Release;
};
@@ -1611,8 +1654,12 @@
isa = XCBuildConfiguration;
baseConfigurationReference = D0C22C0C179CC02E00158214 /* Profile.xcconfig */;
buildSettings = {
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
GCC_NO_COMMON_BLOCKS = YES;
MACOSX_DEPLOYMENT_TARGET = 10.8;
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
};
name = Profile;
};
@@ -1620,6 +1667,8 @@
isa = XCBuildConfiguration;
baseConfigurationReference = D0C22C1A179CC02E00158214 /* Mac-Framework.xcconfig */;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = NO;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = NO;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
FRAMEWORK_VERSION = A;
@@ -1628,6 +1677,7 @@
Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/extobjc,
);
INFOPLIST_FILE = "Squirrel/Squirrel-Info.plist";
PRODUCT_BUNDLE_IDENTIFIER = "com.github.${PRODUCT_NAME:rfc1034identifier}";
WRAPPER_EXTENSION = framework;
};
name = Profile;
@@ -1646,7 +1696,9 @@
);
INFOPLIST_FILE = "SquirrelTests/SquirrelTests-Info.plist";
MACOSX_DEPLOYMENT_TARGET = 10.9;
PRODUCT_BUNDLE_IDENTIFIER = "com.github.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
};
name = Profile;
};
@@ -1654,6 +1706,9 @@
isa = XCBuildConfiguration;
baseConfigurationReference = D0C22C0E179CC02E00158214 /* Test.xcconfig */;
buildSettings = {
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
GCC_NO_COMMON_BLOCKS = YES;
MACOSX_DEPLOYMENT_TARGET = 10.8;
SDKROOT = macosx;
};
@@ -1663,6 +1718,8 @@
isa = XCBuildConfiguration;
baseConfigurationReference = D0C22C1A179CC02E00158214 /* Mac-Framework.xcconfig */;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = NO;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = NO;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
FRAMEWORK_VERSION = A;
@@ -1671,6 +1728,7 @@
Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/extobjc,
);
INFOPLIST_FILE = "Squirrel/Squirrel-Info.plist";
PRODUCT_BUNDLE_IDENTIFIER = "com.github.${PRODUCT_NAME:rfc1034identifier}";
WRAPPER_EXTENSION = framework;
};
name = Test;
@@ -1689,7 +1747,9 @@
);
INFOPLIST_FILE = "SquirrelTests/SquirrelTests-Info.plist";
MACOSX_DEPLOYMENT_TARGET = 10.9;
PRODUCT_BUNDLE_IDENTIFIER = "com.github.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
};
name = Test;
};
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0610"
LastUpgradeVersion = "0830"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
@@ -37,10 +37,10 @@
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "NO"
buildConfiguration = "Debug">
shouldUseLaunchSchemeArgsEnv = "NO">
<Testables>
<TestableReference
skipped = "NO">
@@ -68,17 +68,30 @@
value = "com.github.Squirrel.SquirrelTests"
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "SQUIRREL_TEST_LOCAL_CDN"
value = "True"
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "SQUIRREL_TEST_LOCAL_SERVER"
value = "True"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugXPCServices = "NO"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
@@ -100,10 +113,10 @@
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Profile"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0610"
LastUpgradeVersion = "0830"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -23,10 +23,10 @@
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Test"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Test">
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
@@ -38,17 +38,21 @@
ReferencedContainer = "container:Squirrel.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable>
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D08D4E0B17B451500012B22D"
@@ -61,12 +65,13 @@
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
<BuildableProductRunnable>
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D08D4E0B17B451500012B22D"
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0610"
LastUpgradeVersion = "0830"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -23,21 +23,24 @@
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Test"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Test">
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
@@ -52,10 +55,10 @@
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
@@ -14,4 +14,7 @@
// Info.plist, or nil if the key is not present.
@property (nonatomic, copy, readonly) NSString *sqrl_bundleVersion;
/// The value of the `kCFBundleExecutableKey` key.
@property (nonatomic, copy, readonly) NSString *sqrl_executableName;
@end
@@ -14,4 +14,8 @@
return [self objectForInfoDictionaryKey:(id)kCFBundleVersionKey];
}
- (NSString *)sqrl_executableName {
return [self objectForInfoDictionaryKey:(id)kCFBundleExecutableKey];
}
@end
+12 -2
View File
@@ -29,14 +29,24 @@
// on-disk locations for. This must not be nil.
- (instancetype)initWithApplicationIdentifier:(NSString *)appIdentifier;
// Finds or creates an Application Support folder.
// Finds or creates a storage folder.
//
// Returns a signal which synchronously sends a URL then completes, or errors.
- (RACSignal *)applicationSupportURL;
- (RACSignal *)storageURL;
// Determines where archived `SQRLShipItState` should be saved.
//
// Returns a signal which synchronously sends a URL then completes, or errors.
- (RACSignal *)shipItStateURL;
// Determines where ShipIt's stdout log should be saved.
//
// Returns a signal which synchronously sends a URL then completes, or errors.
- (RACSignal *)shipItStdoutURL;
// Determines where ShipIt's stderr log should be saved.
//
// Returns a signal which synchronously sends a URL then completes, or errors.
- (RACSignal *)shipItStderrURL;
@end
+48 -9
View File
@@ -47,11 +47,11 @@
#pragma mark Folder URLs
- (RACSignal *)applicationSupportURL {
- (RACSignal *)storageURL {
return [[RACSignal
defer:^{
NSError *error = nil;
NSURL *folderURL = [NSFileManager.defaultManager URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&error];
NSURL *folderURL = [NSFileManager.defaultManager URLForDirectory:NSCachesDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&error];
if (folderURL == nil) {
return [RACSignal error:error];
}
@@ -63,16 +63,55 @@
return [RACSignal return:folderURL];
}]
setNameWithFormat:@"%@ -applicationSupportURL", self];
setNameWithFormat:@"%@ -storageURL", self];
}
// Returns a signal to a URL with the given filename and job name inside of the storage folder
//
// filename - The name of the file in the storage folder to return in this URL
// jobname - The name to give the returned RACSignal
// ensureWritable - If set to true, we will append an integer to the end of the filename in order to find a writeable URL.
// This fixes an issue wherein the stdout and stderr files may have been owned by root, and thus prevented ShipIt from launching.
- (RACSignal *)URLForFileNamed:(NSString *)filename withJobNamed:(NSString *)jobname ensureWritable:(BOOL)ensureWritable {
return [[[self
storageURL]
map:^(NSURL *folderURL) {
NSURL *fileURL = [folderURL URLByAppendingPathComponent:filename];
if (ensureWritable) {
int attempts = 0;
while (attempts < 100) {
NSNumber *writable = nil;
NSError *writableError = nil;
BOOL gotWritable = [fileURL getResourceValue:&writable forKey:NSURLIsWritableKey error:&writableError];
if (!gotWritable || writable.boolValue) {
return fileURL;
}
attempts++;
fileURL = [folderURL URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%i", filename, attempts]];
}
NSLog(@"Error: Unable to find writable log file after 100 attempts.");
}
return fileURL;
}]
setNameWithFormat:@"%@ -%@", self, jobname];
}
- (RACSignal *)shipItStateURL {
return [[[self
applicationSupportURL]
map:^(NSURL *folderURL) {
return [folderURL URLByAppendingPathComponent:@"ShipItState.plist"];
}]
setNameWithFormat:@"%@ -shipItStateURL", self];
return [self URLForFileNamed:@"ShipItState.plist" withJobNamed:@"shipItStateURL" ensureWritable:false];
}
- (RACSignal *)shipItStdoutURL {
return [self URLForFileNamed:@"ShipIt_stdout.log" withJobNamed:@"shipItStdoutURL" ensureWritable:true];
}
- (RACSignal *)shipItStderrURL {
return [self URLForFileNamed:@"ShipIt_stderr.log" withJobNamed:@"shipItStderrURL" ensureWritable:true];
}
#pragma mark NSObject
+2 -2
View File
@@ -40,9 +40,9 @@
#pragma mark MTLJSONSerializing
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return [super.JSONKeyPathsByPropertyKey mtl_dictionaryByAddingEntriesFromDictionary:@{
return @{
@keypath(SQRLDownloadedUpdate.new, bundleURL): NSNull.null
}];
};
}
@end
+89 -18
View File
@@ -22,7 +22,6 @@
#import "NSError+SQRLVerbosityExtensions.h"
#import "RACSignal+SQRLTransactionExtensions.h"
#import "SQRLCodeSignature.h"
#import "SQRLDirectoryManager.h"
#import "SQRLShipItRequest.h"
#import "SQRLTerminationListener.h"
#import "SQRLInstallerOwnedBundle.h"
@@ -262,27 +261,49 @@ NSString * const SQRLInstallerOwnedBundleKey = @"SQRLInstallerOwnedBundle";
setNameWithFormat:@"%@ -acquireTargetBundleURLForRequest: %@", self, request];
}
- (RACSignal *)renameIfNeeded:(SQRLShipItRequest *)request updateBundleURL:(NSURL *)updateBundleURL {
if (!request.useUpdateBundleName) return [RACSignal return:request];
return [[self
renamedTargetIfNeededWithTargetURL:request.targetBundleURL sourceURL:updateBundleURL]
flattenMap:^(NSURL *newTargetURL) {
if ([newTargetURL isEqual:request.targetBundleURL]) return [RACSignal return:request];
SQRLShipItRequest *updatedRequest = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:request.updateBundleURL targetBundleURL:newTargetURL bundleIdentifier:request.bundleIdentifier launchAfterInstallation:request.launchAfterInstallation useUpdateBundleName:request.useUpdateBundleName];
return [[self
installItemToURL:newTargetURL fromURL:request.targetBundleURL]
concat:[RACSignal return:updatedRequest]];
}];
}
- (RACSignal *)installRequest:(SQRLShipItRequest *)request {
NSParameterAssert(request != nil);
return [[[[self
prepareAndValidateUpdateBundleURLForRequest:request]
flattenMap:^(NSURL *updateBundleURL) {
return [[[[[[[self
acquireTargetBundleURLForRequest:request]
concat:[self installItemToURL:request.targetBundleURL fromURL:updateBundleURL]]
concat:[RACSignal return:request.updateBundleURL]]
concat:[RACSignal return:updateBundleURL]]
concat:[RACSignal defer:^{
return [RACSignal return:self.ownedBundle.temporaryURL];
}]]
flattenMap:^(NSURL *location) {
return [[[self
deleteOwnedBundleAtURL:location]
doError:^(NSError *error) {
NSLog(@"Couldn't remove owned bundle at location %@, error %@", location, error.sqrl_verboseDescription);
return [[[[self
renameIfNeeded:request updateBundleURL:updateBundleURL]
flattenMap:^(SQRLShipItRequest *request) {
return [[self acquireTargetBundleURLForRequest:request] concat:[RACSignal return:request]];
}]
flattenMap:^(SQRLShipItRequest *request) {
return [[[[[[self
installItemToURL:request.targetBundleURL fromURL:updateBundleURL]
concat:[RACSignal return:request.updateBundleURL]]
concat:[RACSignal return:updateBundleURL]]
concat:[RACSignal defer:^{
return [RACSignal return:self.ownedBundle.temporaryURL];
}]]
flattenMap:^(NSURL *location) {
return [[[self
deleteOwnedBundleAtURL:location]
doError:^(NSError *error) {
NSLog(@"Couldn't remove owned bundle at location %@, error %@", location, error.sqrl_verboseDescription);
}]
catchTo:[RACSignal empty]];
}]
catchTo:[RACSignal empty]];
concat:[RACSignal return:request]];
}]
doCompleted:^{
self.ownedBundle = nil;
@@ -380,7 +401,20 @@ NSString * const SQRLInstallerOwnedBundleKey = @"SQRLInstallerOwnedBundle";
if (rmdir(temporaryDirectoryURL.path.fileSystemRepresentation) == 0) {
return [RACSignal empty];
} else {
return [RACSignal error:[NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]];
int code = errno;
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
const char *desc = strerror(code);
if (desc != NULL) {
userInfo[NSLocalizedDescriptionKey] = @(desc);
} else {
userInfo[NSLocalizedDescriptionKey] = NSLocalizedString(@"Unknown POSIX error", @"");
}
userInfo[NSLocalizedFailureReasonErrorKey] = [NSString stringWithFormat:NSLocalizedString(@"Couldn't remove temp dir \"%@\"", @""), temporaryDirectoryURL.path];
userInfo[NSURLErrorKey] = temporaryDirectoryURL;
return [RACSignal error:[NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]];
}
}]
setNameWithFormat:@"%@ -deleteOwnedBundleAtURL: %@", self, bundleURL];
@@ -413,6 +447,36 @@ NSString * const SQRLInstallerOwnedBundleKey = @"SQRLInstallerOwnedBundle";
#pragma mark Installation
/// Check if the target should be renamed and provide the renamed URL.
///
/// targetURL - The URL for the target. Cannot be nil.
/// sourceURL - The URL for the source. Cannot be nil.
///
/// Returns a signal which will send the URL for the renamed target. If a rename
/// isn't needed then it will send `targetURL`.
- (RACSignal *)renamedTargetIfNeededWithTargetURL:(NSURL *)targetURL sourceURL:(NSURL *)sourceURL {
return [RACSignal defer:^{
NSBundle *sourceBundle = [NSBundle bundleWithURL:sourceURL];
NSString *targetExecutableName = targetURL.lastPathComponent.stringByDeletingPathExtension;
NSString *sourceExecutableName = sourceBundle.sqrl_executableName;
// If they're already the same then we're good.
if ([targetExecutableName isEqual:sourceExecutableName]) {
return [RACSignal return:targetURL];
}
NSString *newAppName = [sourceExecutableName stringByAppendingPathExtension:@"app"];
NSURL *newTargetURL = [targetURL.URLByDeletingLastPathComponent URLByAppendingPathComponent:newAppName];
// If there's already something there then don't rename to it.
if ([NSFileManager.defaultManager fileExistsAtPath:newTargetURL.path]) {
return [RACSignal return:targetURL];
}
return [RACSignal return:newTargetURL];
}];
}
- (RACSignal *)installItemToURL:(NSURL *)targetURL fromURL:(NSURL *)sourceURL {
NSParameterAssert(targetURL != nil);
NSParameterAssert(sourceURL != nil);
@@ -453,7 +517,7 @@ NSString * const SQRLInstallerOwnedBundleKey = @"SQRLInstallerOwnedBundle";
setNameWithFormat:@"%@ -installItemAtURL: %@ fromURL: %@", self, targetURL, sourceURL];
}
#pragma Quarantine Bit Removal
#pragma mark Quarantine Bit Removal
- (RACSignal *)clearQuarantineForDirectory:(NSURL *)directory {
NSParameterAssert(directory != nil);
@@ -479,7 +543,14 @@ NSString * const SQRLInstallerOwnedBundleKey = @"SQRLInstallerOwnedBundle";
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
const char *desc = strerror(code);
if (desc != NULL) userInfo[NSLocalizedDescriptionKey] = @(desc);
if (desc != NULL) {
userInfo[NSLocalizedDescriptionKey] = @(desc);
} else {
userInfo[NSLocalizedDescriptionKey] = NSLocalizedString(@"Unknown POSIX error", @"");
}
userInfo[NSLocalizedFailureReasonErrorKey] = [NSString stringWithFormat:NSLocalizedString(@"Couldn't remove quarantine attribute from \"%@\". This most likely means the file is read-only.", @""), URL.path];
userInfo[NSURLErrorKey] = URL;
return [RACSignal error:[NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:userInfo]];
}
+19 -4
View File
@@ -31,9 +31,13 @@ const NSInteger SQRLShipItLauncherErrorCouldNotStartService = 1;
return [[[RACSignal
defer:^{
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:jobLabel];
return [directoryManager applicationSupportURL];
return [RACSignal zip:@[
[directoryManager shipItStdoutURL],
[directoryManager shipItStderrURL],
[directoryManager shipItStateURL]
]];
}]
map:^(NSURL *appSupportURL) {
reduceEach:^(NSURL *stdoutURL, NSURL *stderrURL, NSURL *shipItStateURL) {
NSBundle *squirrelBundle = [NSBundle bundleForClass:self.class];
NSAssert(squirrelBundle != nil, @"Could not open Squirrel.framework bundle");
@@ -56,9 +60,15 @@ const NSInteger SQRLShipItLauncherErrorCouldNotStartService = 1;
// Pass in the service name so ShipIt knows how to broadcast itself.
[arguments addObject:jobLabel];
// We need to pass the path to ShipIt rather than having ShipIt
// calculate the path itself. This is because ShipIt may run as a
// different user if it had to escalate to be able to replace the
// bundle.
[arguments addObject:shipItStateURL.path];
jobDict[@(LAUNCH_JOBKEY_PROGRAMARGUMENTS)] = arguments;
jobDict[@(LAUNCH_JOBKEY_STANDARDOUTPATH)] = [appSupportURL URLByAppendingPathComponent:@"ShipIt_stdout.log"].path;
jobDict[@(LAUNCH_JOBKEY_STANDARDERRORPATH)] = [appSupportURL URLByAppendingPathComponent:@"ShipIt_stderr.log"].path;
jobDict[@(LAUNCH_JOBKEY_STANDARDOUTPATH)] = stdoutURL.path;
jobDict[@(LAUNCH_JOBKEY_STANDARDERRORPATH)] = stderrURL.path;
return jobDict;
}]
@@ -117,6 +127,9 @@ const NSInteger SQRLShipItLauncherErrorCouldNotStartService = 1;
setNameWithFormat:@"+shipItAuthorization"];
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ (RACSignal *)launchPrivileged:(BOOL)privileged {
return [[[RACSignal
zip:@[
@@ -147,4 +160,6 @@ const NSInteger SQRLShipItLauncherErrorCouldNotStartService = 1;
setNameWithFormat:@"+launchPrivileged: %i", (int)privileged];
}
#pragma clang diagnostic pop
@end
+5 -1
View File
@@ -73,10 +73,11 @@ extern NSString * const SQRLShipItRequestPropertyErrorKey;
// installing. Can be nil.
// launchAfterInstallation - Whether the updated application should be launched
// after installation.
// useUpdateBundleName - Should the target use the update bundle's name?
//
// Returns a request which can be written to disk for ShipIt to read and
// perform.
- (instancetype)initWithUpdateBundleURL:(NSURL *)updateBundleURL targetBundleURL:(NSURL *)targetBundleURL bundleIdentifier:(NSString *)bundleIdentifier launchAfterInstallation:(BOOL)launchAfterInstallation;
- (instancetype)initWithUpdateBundleURL:(NSURL *)updateBundleURL targetBundleURL:(NSURL *)targetBundleURL bundleIdentifier:(NSString *)bundleIdentifier launchAfterInstallation:(BOOL)launchAfterInstallation useUpdateBundleName:(BOOL)useUpdateBundleName;
// The URL to the downloaded update's app bundle.
@property (nonatomic, copy, readonly) NSURL *updateBundleURL;
@@ -93,4 +94,7 @@ extern NSString * const SQRLShipItRequestPropertyErrorKey;
// Whether to launch the application after an update is successfully installed.
@property (nonatomic, assign, readonly) BOOL launchAfterInstallation;
// Whether the app should use the update bundle's name.
@property (nonatomic, assign, readonly) BOOL useUpdateBundleName;
@end
+2 -1
View File
@@ -47,12 +47,13 @@ NSString * const SQRLShipItRequestPropertyErrorKey = @"SQRLShipItRequestProperty
return self;
}
- (instancetype)initWithUpdateBundleURL:(NSURL *)updateBundleURL targetBundleURL:(NSURL *)targetBundleURL bundleIdentifier:(NSString *)bundleIdentifier launchAfterInstallation:(BOOL)launchAfterInstallation {
- (instancetype)initWithUpdateBundleURL:(NSURL *)updateBundleURL targetBundleURL:(NSURL *)targetBundleURL bundleIdentifier:(NSString *)bundleIdentifier launchAfterInstallation:(BOOL)launchAfterInstallation useUpdateBundleName:(BOOL)useUpdateBundleName {
return [self initWithDictionary:@{
@keypath(self.updateBundleURL): updateBundleURL,
@keypath(self.targetBundleURL): targetBundleURL,
@keypath(self.bundleIdentifier): bundleIdentifier ?: NSNull.null,
@keypath(self.launchAfterInstallation): @(launchAfterInstallation),
@keypath(self.useUpdateBundleName): @(useUpdateBundleName),
} error:NULL];
}
+1 -1
View File
@@ -74,7 +74,7 @@ NSString * const SQRLUpdateJSONPublicationDateKey = @"pub_date";
+ (NSValueTransformer *)releaseDateJSONTransformer {
// ISO 8601 Time Zone with ':'
NSString * const ISO8601DateFormat = @"yyyy'-'MM'-'DD'T'HH':'mm':'ssZZZZZ";
NSString * const ISO8601DateFormat = @"yyyy'-'MM'-'dd'T'HH':'mm':'ssZZZZZ";
return [MTLValueTransformer reversibleTransformerWithForwardBlock:^ NSDate * (NSString *dateString) {
if (![dateString isKindOfClass:NSString.class]) return nil;
+60
View File
@@ -23,6 +23,9 @@ typedef enum : NSUInteger {
SQRLUpdaterStateAwaitingRelaunch,
} SQRLUpdaterState;
// Block for providing download requests given a download url
typedef NSURLRequest * (^SQRLRequestForDownload)(NSURL *);
// The domain for errors originating within SQRLUpdater.
extern NSString * const SQRLUpdaterErrorDomain;
@@ -64,6 +67,12 @@ extern NSString * const SQRLUpdaterJSONObjectErrorKey;
@class RACDisposable;
@class RACSignal;
/// Type of mode used to download the release
typedef enum {
JSONFILE=1,
RELEASESERVER
} SQRLUpdaterMode;
// Checks for, downloads, and installs updates.
@interface SQRLUpdater : NSObject
@@ -92,6 +101,15 @@ extern NSString * const SQRLUpdaterJSONObjectErrorKey;
// This property must never be set to nil.
@property (atomic, copy) NSURLRequest *updateRequest;
// The block used for fetching a given download request
//
// The default value is the argument that was originally passed to
// -initWithUpdateRequest:requestForDownload:.
//
// If initialized with -initWithUpdateRequest: this block will
// return a generic NSURLRequest with the provided url.
@property (nonatomic, copy) SQRLRequestForDownload requestForDownload;
// The `SQRLUpdate` subclass to instantiate with the server's response.
//
// By default, this is `SQRLUpdate` itself, but it can be set to a custom
@@ -111,6 +129,45 @@ extern NSString * const SQRLUpdaterJSONObjectErrorKey;
// Returns the initialized `SQRLUpdater`.
- (id)initWithUpdateRequest:(NSURLRequest *)updateRequest;
// Initializes an updater that will send the given request to check for updates
// on a CDN reading a release file in json format.
//
// updateRequest - A request to send to check for updates. This request can be
// customized as desired, like by including an `Authorization`
// header to authenticate with a private update server, or
// pointing to a local URL for testing. This must not be nil.
// forVersion - the currently installed version
//
// Returns the initialized `SQRLUpdater`.
- (id)initWithUpdateRequest:(NSURLRequest *)updateRequest
forVersion:(NSString*)version;
// Initializes an updater that will send the given request to check for updates
// and passes a block to provide requests for the update downloads.
//
// updateRequest - Same as with initWithUpdateRequest
// requestForDownload - Once the update url is found for the update download, allow
// providing custom requests that can be costomized as desired.
// Useful for including `Authorization` headers just like the
// updateRequest param.
//
// Returns the initialized `SQRLUpdater`.
- (id)initWithUpdateRequest:(NSURLRequest *)updateRequest requestForDownload:(SQRLRequestForDownload)requestForDownload;
// Initializes an updater that will send the given request to check for updates
// and passes a block to provide requests for the update downloads.
//
// updateRequest - Same as with initWithUpdateRequest
// requestForDownload - Once the update url is found for the update download, allow
// providing custom requests that can be costomized as desired.
// Useful for including `Authorization` headers just like the
// updateRequest param.
// forVersion - currently running version
// useMode - either RELEASESERVER or JSONFILE
//
// Returns the initialized `SQRLUpdater`.
- (id)initWithUpdateRequest:(NSURLRequest *)updateRequest requestForDownload:(SQRLRequestForDownload)requestForDownload forVersion:(NSString*) version useMode:(SQRLUpdaterMode) mode;
// Executes `checkForUpdatesCommand` (if enabled) every `interval` seconds.
//
// The first check will not occur until `interval` seconds have passed.
@@ -136,6 +193,9 @@ extern NSString * const SQRLUpdaterJSONObjectErrorKey;
// wrong before termination. The signal will never complete.
- (RACSignal *)relaunchToInstallUpdate;
- (BOOL)isRunningOnReadOnlyVolume;
- (RACSignal *)updateFromJSONData:(NSData *)data;
@end
@interface SQRLUpdater (Unavailable)
+300 -100
View File
@@ -20,6 +20,7 @@
#import "SQRLShipItRequest.h"
#import <ReactiveCocoa/EXTScope.h>
#import <ReactiveCocoa/ReactiveCocoa.h>
#import <sys/mount.h>
NSString * const SQRLUpdaterErrorDomain = @"SQRLUpdaterErrorDomain";
NSString * const SQRLUpdaterServerDataErrorKey = @"SQRLUpdaterServerDataErrorKey";
@@ -32,6 +33,11 @@ const NSInteger SQRLUpdaterErrorInvalidServerResponse = 5;
const NSInteger SQRLUpdaterErrorInvalidJSON = 6;
const NSInteger SQRLUpdaterErrorInvalidServerBody = 7;
/// The application's being run on a read-only volume.
const NSInteger SQRLUpdaterErrorReadOnlyVolume = 8;
const NSTimeInterval SQURLUpdaterZipDownloadTimeoutSeconds = 20 * 60;
// The prefix used when creating temporary directories for updates. This will be
// followed by a random string of characters.
static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
@@ -40,6 +46,10 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
@property (atomic, readwrite) SQRLUpdaterState state;
/// The etag of the currently downloaded update, nil if no update has been
/// downloaded.
@property (atomic, copy) NSString *etag;
// The code signature for the running application, used to check updates before
// sending them to ShipIt.
@property (nonatomic, strong, readonly) SQRLCodeSignature *signature;
@@ -49,16 +59,6 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
// Sends completed or error.
@property (nonatomic, strong, readonly) RACSignal *shipItLauncher;
// Lazily removes outdated temporary directories (used for previous updates)
// upon first subscription.
//
// Pruning directories while an update is pending or in progress will result in
// undefined behavior.
//
// Sends each removed directory then completes, or errors, on an unspecified
// thread.
@property (nonatomic, strong, readonly) RACSignal *prunedUpdateDirectories;
// Parses an update model from downloaded data.
//
// data - JSON data representing an update manifest. This must not be nil.
@@ -146,14 +146,46 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
}
- (id)initWithUpdateRequest:(NSURLRequest *)updateRequest {
return [self initWithUpdateRequest:updateRequest requestForDownload:^(NSURL *downloadURL) {
return [NSURLRequest requestWithURL:downloadURL];
}];
}
- (id)initWithUpdateRequest:(NSURLRequest *)updateRequest forVersion: (NSString*) version {
return [self initWithUpdateRequest:updateRequest requestForDownload:^(NSURL *downloadURL) {
return [NSURLRequest requestWithURL:downloadURL];
} forVersion:version useMode:JSONFILE];
}
- (id)initWithUpdateRequest:(NSURLRequest *)updateRequest requestForDownload:(SQRLRequestForDownload)requestForDownload {
return [self initWithUpdateRequest:updateRequest requestForDownload:^(NSURL *downloadURL) {
return [NSURLRequest requestWithURL:downloadURL];
} forVersion:nil useMode:RELEASESERVER];
}
- (id)initWithUpdateRequest:(NSURLRequest *)updateRequest requestForDownload:(SQRLRequestForDownload)requestForDownload
forVersion:(NSString*) version useMode:(SQRLUpdaterMode) mode {
//! download simple file
NSParameterAssert(updateRequest != nil);
NSParameterAssert(requestForDownload != nil);
if (mode == JSONFILE) {
NSParameterAssert(version != nil);
}
self = [super init];
if (self == nil) return nil;
_updateRequest = [updateRequest copy];
_updateClass = SQRLUpdate.class;
_requestForDownload = [requestForDownload copy];
NSMutableURLRequest* mutableUpdateRequest = [updateRequest mutableCopy];
if (mode == JSONFILE) {
mutableUpdateRequest.cachePolicy = NSURLRequestReloadIgnoringCacheData;
}
_updateRequest = mutableUpdateRequest;
_updateClass = SQRLUpdate.class;
NSError *error = nil;
_signature = [SQRLCodeSignature currentApplicationSignature:&error];
if (_signature == nil) {
@@ -169,33 +201,6 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
BOOL updatesDisabled = (getenv("DISABLE_UPDATE_CHECK") != NULL);
@weakify(self);
_prunedUpdateDirectories = [[[[RACSignal
defer:^{
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel];
return [directoryManager applicationSupportURL];
}]
flattenMap:^(NSURL *appSupportURL) {
NSFileManager *manager = [[NSFileManager alloc] init];
NSDirectoryEnumerator *enumerator = [manager enumeratorAtURL:appSupportURL includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsSubdirectoryDescendants errorHandler:^(NSURL *URL, NSError *error) {
NSLog(@"Error enumerating item %@ within directory %@: %@", URL, appSupportURL, error);
return YES;
}];
return [[enumerator.rac_sequence.signal
filter:^(NSURL *enumeratedURL) {
NSString *name = enumeratedURL.lastPathComponent;
return [name hasPrefix:SQRLUpdaterUniqueTemporaryDirectoryPrefix];
}]
doNext:^(NSURL *directoryURL) {
NSError *error = nil;
if (![manager removeItemAtURL:directoryURL error:&error]) {
NSLog(@"Error removing old update directory at %@: %@", directoryURL, error.sqrl_verboseDescription);
}
}];
}]
replayLazily]
setNameWithFormat:@"%@ -prunedUpdateDirectories", self];
_checkForUpdatesCommand = [[RACCommand alloc] initWithEnabled:[RACSignal return:@(!updatesDisabled)] signalBlock:^(id _) {
@strongify(self);
NSParameterAssert(self.updateRequest != nil);
@@ -204,12 +209,10 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
NSMutableURLRequest *request = [self.updateRequest mutableCopy];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
// Prune old updates before the first update check.
return [[[[[[[[self.prunedUpdateDirectories
catch:^(NSError *error) {
NSLog(@"Error pruning old updates: %@", error);
return [RACSignal empty];
}]
return [[[[[[[[self
performHousekeeping]
//! get file from server
then:^{
self.state = SQRLUpdaterStateCheckingForUpdate;
@@ -218,21 +221,82 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
reduceEach:^(NSURLResponse *response, NSData *bodyData) {
if ([response isKindOfClass:NSHTTPURLResponse.class]) {
NSHTTPURLResponse *httpResponse = (id)response;
if (!(httpResponse.statusCode >= 200 && httpResponse.statusCode <= 299)) {
BOOL readOnlyVolume = [self isRunningOnReadOnlyVolume];
if (readOnlyVolume) {
NSDictionary *errorInfo = @{
NSLocalizedDescriptionKey: NSLocalizedString(@"Update check failed", nil),
NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"The server sent an invalid response. Try again later.", nil),
SQRLUpdaterServerDataErrorKey: bodyData,
NSLocalizedDescriptionKey: NSLocalizedString(@"Cannot update while running on a read-only volume", nil),
NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"The application is on a read-only volume. Please move the application and try again. If you're on macOS Sierra or later, you'll need to move the application out of the Downloads directory. See https://github.com/Squirrel/Squirrel.Mac/issues/182 for more information.", nil),
};
NSError *error = [NSError errorWithDomain:SQRLUpdaterErrorDomain code:SQRLUpdaterErrorInvalidServerResponse userInfo:errorInfo];
NSError *error = [NSError errorWithDomain:SQRLUpdaterErrorDomain code:SQRLUpdaterErrorReadOnlyVolume userInfo:errorInfo];
return [RACSignal error:error];
}
if (httpResponse.statusCode == 204 /* No Content */) {
return [RACSignal empty];
if (mode == RELEASESERVER) {
if (!(httpResponse.statusCode >= 200 && httpResponse.statusCode <= 299)) {
NSDictionary *errorInfo = @{
NSLocalizedDescriptionKey: NSLocalizedString(@"Update check failed", nil),
NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"The server sent an invalid response. Try again later.", nil),
SQRLUpdaterServerDataErrorKey: bodyData,
};
NSError *error = [NSError errorWithDomain:SQRLUpdaterErrorDomain code:SQRLUpdaterErrorInvalidServerResponse userInfo:errorInfo];
return [RACSignal error:error];
}
if (httpResponse.statusCode == 204 /* No Content */) {
return [RACSignal empty];
}
} else if (mode == JSONFILE) {
NSError *error = nil;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:bodyData options:0 error:&error];
if (dict == nil) {
NSMutableDictionary *userInfo = [error.userInfo mutableCopy] ?: [NSMutableDictionary dictionary];
userInfo[NSLocalizedDescriptionKey] = NSLocalizedString(@"Update check failed", nil);
userInfo[NSLocalizedRecoverySuggestionErrorKey] = NSLocalizedString(@"The server sent an invalid response. Try again later.", nil);
userInfo[SQRLUpdaterServerDataErrorKey] = bodyData;
if (error != nil) userInfo[NSUnderlyingErrorKey] = error;
return [RACSignal error:[NSError errorWithDomain:SQRLUpdaterErrorDomain code:SQRLUpdaterErrorInvalidServerBody userInfo:userInfo]];
}
NSString *currentRelease = dict[@"currentRelease"];
if(currentRelease) {
//! if CDN points to the currently running version as the latest version, bail out
if([currentRelease isEqualToString:version]) {
NSLog(@"The running client is already the latest version.");
return [RACSignal empty];
}
if ([version compare:currentRelease options:NSNumericSearch] == NSOrderedDescending) {
// currentRelease is lower than version.
// Might be a new version for testing that is not deployed yet
// no roll back
NSLog(@"The running client is newer than the latest deployed release. Not downgrading.");
return [RACSignal empty];
}
//! @todo find latest
NSArray *releases = dict[@"releases"];
for(NSDictionary* release in releases) {
if([currentRelease isEqualToString:release[@"version"]]) {
bodyData = [NSJSONSerialization dataWithJSONObject:release[@"updateTo"]
options:0 error:&error];
break;
}
}
}
}
}
if (bodyData == nil) {
NSMutableDictionary *userInfo = [error.userInfo mutableCopy] ?: [NSMutableDictionary dictionary];
userInfo[NSLocalizedDescriptionKey] = NSLocalizedString(@"Update check failed", nil);
userInfo[NSLocalizedRecoverySuggestionErrorKey] = NSLocalizedString(@"The server sent an invalid response. Try again later.", nil);
userInfo[SQRLUpdaterServerDataErrorKey] = bodyData;
if (error != nil) userInfo[NSUnderlyingErrorKey] = error;
return [RACSignal error:[NSError errorWithDomain:SQRLUpdaterErrorDomain code:SQRLUpdaterErrorInvalidServerBody userInfo:userInfo]];
}
return [RACSignal return:bodyData];
}]
flatten]
@@ -259,15 +323,13 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
_shipItLauncher = [[[RACSignal
defer:^{
@strongify(self);
NSURL *targetURL = NSRunningApplication.currentApplication.bundleURL;
NSNumber *targetWritable = nil;
NSError *targetWritableError = nil;
BOOL gotWritable = [targetURL getResourceValue:&targetWritable forKey:NSURLIsWritableKey error:&targetWritableError];
// If we can't determine whether it can be written, assume nonprivileged and
// wait for another, more canonical error.
return [SQRLShipItLauncher launchPrivileged:(gotWritable && !targetWritable.boolValue)];
BOOL targetWritable = [self canWriteToURL:targetURL];
BOOL parentWritable = [self canWriteToURL:targetURL.URLByDeletingLastPathComponent];
return [SQRLShipItLauncher launchPrivileged:!targetWritable || !parentWritable];
}]
replayLazily]
setNameWithFormat:@"shipItLauncher"];
@@ -275,6 +337,15 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
return self;
}
- (BOOL)canWriteToURL:(NSURL *)fileURL {
NSNumber *writable = nil;
NSError *writableError = nil;
BOOL gotWritable = [fileURL getResourceValue:&writable forKey:NSURLIsWritableKey error:&writableError];
// If we can't determine whether it can be written, assume nonprivileged and
// wait for another, more canonical error.
return !gotWritable || writable.boolValue;
}
#pragma mark Checking for Updates
- (RACDisposable *)startAutomaticChecksWithInterval:(NSTimeInterval)interval {
@@ -341,36 +412,90 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
return [[[self
uniqueTemporaryDirectoryForUpdate]
flattenMap:^(NSURL *downloadDirectory) {
void (^cleanUp)(void) = ^{
NSError *error;
if (![NSFileManager.defaultManager removeItemAtURL:downloadDirectory error:&error]) {
NSLog(@"Error removing temporary download directory at %@: %@", downloadDirectory, error.sqrl_verboseDescription);
}
};
return [[[self
downloadBundleForUpdate:update intoDirectory:downloadDirectory]
flattenMap:^(NSBundle *updateBundle) {
// If the bundle is nil it means our conditional GET told us
// we already downloaded the update. So just clean up.
if (updateBundle == nil) {
cleanUp();
return [RACSignal empty];
}
return [self verifyAndPrepareUpdate:update fromBundle:updateBundle];
}]
doError:^(id _) {
NSError *error = nil;
if (![NSFileManager.defaultManager removeItemAtURL:downloadDirectory error:&error]) {
NSLog(@"Error removing temporary download directory at %@: %@", downloadDirectory, error.sqrl_verboseDescription);
}
cleanUp();
}];
}]
setNameWithFormat:@"%@ -downloadAndPrepareUpdate: %@", self, update];
}
- (RACSignal *)unarchiveAndPrepareData:(NSData *)data withName:(NSString *)name intoDirectory:(NSURL *)downloadDirectory {
return [[[[[RACSignal
defer:^{
NSURL *zipOutputURL = [downloadDirectory URLByAppendingPathComponent:name];
NSError *error = nil;
if ([data writeToURL:zipOutputURL options:NSDataWritingAtomic error:&error]) {
return [RACSignal return:zipOutputURL];
} else {
return [RACSignal error:error];
}
}]
doNext:^(NSURL *zipOutputURL) {
NSLog(@"Download completed to: %@", zipOutputURL);
}]
flattenMap:^(NSURL *zipOutputURL) {
return [[[[SQRLZipArchiver
unzipArchiveAtURL:zipOutputURL intoDirectoryAtURL:downloadDirectory]
ignoreValues]
concat:[RACSignal return:zipOutputURL]]
doCompleted:^{
NSError *error = nil;
if (![NSFileManager.defaultManager removeItemAtURL:zipOutputURL error:&error]) {
NSLog(@"Error removing downloaded archive at %@: %@", zipOutputURL, error.sqrl_verboseDescription);
}
}];
}]
flattenMap:^(NSURL *zipOutputURL) {
return [self updateBundleMatchingCurrentApplicationInDirectory:downloadDirectory];
}]
setNameWithFormat:@"%@ -unarchiveAndPrepareData:withName: %@ intoDirectory: %@", self, name, downloadDirectory];
}
- (RACSignal *)downloadBundleForUpdate:(SQRLUpdate *)update intoDirectory:(NSURL *)downloadDirectory {
NSParameterAssert(update != nil);
NSParameterAssert(downloadDirectory != nil);
return [[[[[RACSignal
return [[RACSignal
defer:^{
NSURL *zipDownloadURL = update.updateURL;
NSMutableURLRequest *zipDownloadRequest = [NSMutableURLRequest requestWithURL:zipDownloadURL];
[zipDownloadRequest setValue:@"application/zip" forHTTPHeaderField:@"Accept"];
NSMutableURLRequest *zipDownloadRequest = [self.requestForDownload(zipDownloadURL) mutableCopy];
return [[[[NSURLConnection
[zipDownloadRequest setValue:@"application/zip" forHTTPHeaderField:@"Accept"];
if (self.etag != nil) {
[zipDownloadRequest setValue:self.etag forHTTPHeaderField:@"If-None-Match"];
}
[zipDownloadRequest setTimeoutInterval:SQURLUpdaterZipDownloadTimeoutSeconds];
return [[[NSURLConnection
rac_sendAsynchronousRequest:zipDownloadRequest]
reduceEach:^(NSURLResponse *response, NSData *bodyData) {
if ([response isKindOfClass:NSHTTPURLResponse.class]) {
NSHTTPURLResponse *httpResponse = (id)response;
if (httpResponse.statusCode == 304 /* Not Modified */) {
return [RACSignal return:nil];
}
if (!(httpResponse.statusCode >= 200 && httpResponse.statusCode <= 299)) {
NSDictionary *errorInfo = @{
NSLocalizedDescriptionKey: NSLocalizedString(@"Update download failed", nil),
@@ -380,37 +505,13 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
NSError *error = [NSError errorWithDomain:SQRLUpdaterErrorDomain code:SQRLUpdaterErrorInvalidServerResponse userInfo:errorInfo];
return [RACSignal error:error];
}
self.etag = httpResponse.allHeaderFields[@"ETag"];
}
return [RACSignal return:bodyData];
return [self unarchiveAndPrepareData:bodyData withName:zipDownloadURL.lastPathComponent intoDirectory:downloadDirectory];
}]
flatten]
flattenMap:^(NSData *data) {
NSURL *zipOutputURL = [downloadDirectory URLByAppendingPathComponent:zipDownloadURL.lastPathComponent];
NSError *error = nil;
if ([data writeToURL:zipOutputURL options:NSDataWritingAtomic error:&error]) {
return [RACSignal return:zipOutputURL];
} else {
return [RACSignal error:error];
}
}];
}]
doNext:^(NSURL *zipOutputURL) {
NSLog(@"Download completed to: %@", zipOutputURL);
}]
flattenMap:^(NSURL *zipOutputURL) {
return [[SQRLZipArchiver
unzipArchiveAtURL:zipOutputURL intoDirectoryAtURL:downloadDirectory]
doCompleted:^{
NSError *error = nil;
if (![NSFileManager.defaultManager removeItemAtURL:zipOutputURL error:&error]) {
NSLog(@"Error removing downloaded archive at %@: %@", zipOutputURL, error.sqrl_verboseDescription);
}
}];
}]
then:^{
return [self updateBundleMatchingCurrentApplicationInDirectory:downloadDirectory];
flatten];
}]
setNameWithFormat:@"%@ -downloadBundleForUpdate: %@ intoDirectory: %@", self, update, downloadDirectory];
}
@@ -421,10 +522,10 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
return [[[RACSignal
defer:^{
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel];
return [directoryManager applicationSupportURL];
return [directoryManager storageURL];
}]
flattenMap:^(NSURL *appSupportURL) {
NSURL *updateDirectoryTemplate = [appSupportURL URLByAppendingPathComponent:[SQRLUpdaterUniqueTemporaryDirectoryPrefix stringByAppendingString:@"XXXXXXX"]];
flattenMap:^(NSURL *storageURL) {
NSURL *updateDirectoryTemplate = [storageURL URLByAppendingPathComponent:[SQRLUpdaterUniqueTemporaryDirectoryPrefix stringByAppendingString:@"XXXXXXX"]];
char *updateDirectoryCString = strdup(updateDirectoryTemplate.path.fileSystemRepresentation);
@onExit {
free(updateDirectoryCString);
@@ -502,6 +603,100 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
setNameWithFormat:@"%@ -shipItStateURL", self];
}
/// Is the host app running on a read-only volume?
- (BOOL)isRunningOnReadOnlyVolume {
struct statfs statfsInfo;
NSURL *bundleURL = NSRunningApplication.currentApplication.bundleURL;
int result = statfs(bundleURL.fileSystemRepresentation, &statfsInfo);
if (result == 0) {
return (statfsInfo.f_flags & MNT_RDONLY) != 0;
} else {
// If we can't even check if the volume is read-only, assume it is.
return true;
}
}
- (RACSignal *)performHousekeeping {
return [[RACSignal
merge:@[ [self pruneUpdateDirectories], [self truncateLogs] ]]
catch:^(NSError *error) {
NSLog(@"Error doing housekeeping: %@", error);
return [RACSignal empty];
}];
}
/// Lazily removes outdated temporary directories (used for previous updates)
/// upon subscription.
///
/// Pruning directories while an update is pending or in progress will result in
/// undefined behavior.
///
/// Sends each removed directory then completes, or errors, on an unspecified
/// thread.
- (RACSignal *)pruneUpdateDirectories {
return [[[RACSignal
defer:^{
// If we already have updates downloaded we don't wanna prune them.
if (self.state == SQRLUpdaterStateAwaitingRelaunch) return [RACSignal empty];
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel];
return [directoryManager storageURL];
}]
flattenMap:^(NSURL *storageURL) {
NSFileManager *manager = [[NSFileManager alloc] init];
NSDirectoryEnumerator *enumerator = [manager enumeratorAtURL:storageURL includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsSubdirectoryDescendants errorHandler:^(NSURL *URL, NSError *error) {
NSLog(@"Error enumerating item %@ within directory %@: %@", URL, storageURL, error);
return YES;
}];
return [[enumerator.rac_sequence.signal
filter:^(NSURL *enumeratedURL) {
NSString *name = enumeratedURL.lastPathComponent;
return [name hasPrefix:SQRLUpdaterUniqueTemporaryDirectoryPrefix];
}]
doNext:^(NSURL *directoryURL) {
NSError *error = nil;
if (![manager removeItemAtURL:directoryURL error:&error]) {
NSLog(@"Error removing old update directory at %@: %@", directoryURL, error.sqrl_verboseDescription);
}
}];
}]
setNameWithFormat:@"%@ -prunedUpdateDirectories", self];
}
// Like truncation, but backwards.
- (RACSignal *)backwardTruncateFile:(NSURL *)fileURL {
return [RACSignal defer:^{
static const NSInteger MAX_LENGTH = 1024 * 1024 * 8;
NSError *error;
NSFileHandle *handle = [NSFileHandle fileHandleForWritingToURL:fileURL error:&error];
if (handle == nil) return [RACSignal error:error];
unsigned long long fileLength = [handle seekToEndOfFile];
if (fileLength <= MAX_LENGTH) return [RACSignal empty];
[handle seekToFileOffset:fileLength - MAX_LENGTH];
NSData *mostRecentData = [handle readDataToEndOfFile];
[handle truncateFileAtOffset:0];
[handle writeData:mostRecentData];
return [RACSignal empty];
}];
}
- (RACSignal *)truncateLogs {
return [[RACSignal
defer:^{
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel];
return [RACSignal zip:@[ [directoryManager shipItStdoutURL], [directoryManager shipItStderrURL] ]];
}]
reduceEach:^(NSURL *stdoutURL, NSURL *stderrURL) {
return [RACSignal merge:@[ [self backwardTruncateFile:stdoutURL], [self backwardTruncateFile:stderrURL] ]];
}];
}
#pragma mark Installing Updates
- (RACSignal *)verifyAndPrepareUpdate:(SQRLUpdate *)update fromBundle:(NSBundle *)updateBundle {
@@ -528,7 +723,12 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
return [[[[RACSignal
defer:^{
NSRunningApplication *currentApplication = NSRunningApplication.currentApplication;
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:update.bundle.bundleURL targetBundleURL:currentApplication.bundleURL bundleIdentifier:currentApplication.bundleIdentifier launchAfterInstallation:NO];
NSBundle *appBundle = [NSBundle bundleWithURL:currentApplication.bundleURL];
// Only use the update bundle's name if the user hasn't renamed the
// app themselves.
BOOL useUpdateBundleName = [appBundle.sqrl_executableName isEqual:currentApplication.bundleURL.lastPathComponent.stringByDeletingPathExtension];
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:update.bundle.bundleURL targetBundleURL:currentApplication.bundleURL bundleIdentifier:currentApplication.bundleIdentifier launchAfterInstallation:NO useUpdateBundleName:useUpdateBundleName];
return [request writeUsingURL:self.shipItStateURL];
}]
then:^{
@@ -542,7 +742,7 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
return [[[[[[[[SQRLShipItRequest
readUsingURL:self.shipItStateURL]
map:^(SQRLShipItRequest *request) {
return [[SQRLShipItRequest alloc] initWithUpdateBundleURL:request.updateBundleURL targetBundleURL:request.targetBundleURL bundleIdentifier:request.bundleIdentifier launchAfterInstallation:YES];
return [[SQRLShipItRequest alloc] initWithUpdateBundleURL:request.updateBundleURL targetBundleURL:request.targetBundleURL bundleIdentifier:request.bundleIdentifier launchAfterInstallation:YES useUpdateBundleName:request.useUpdateBundleName];
}]
flattenMap:^(SQRLShipItRequest *request) {
return [[request
+2 -1
View File
@@ -54,7 +54,7 @@ const NSInteger SQRLZipArchiverShellTaskFailed = 1;
_dittoTask = [[NSTask alloc] init];
_dittoTask.launchPath = @"/usr/bin/ditto";
_dittoTask.environment = @{ @"DITTOABORT": @1 };
_dittoTask.environment = @{ @"DITTOABORT": @"1" };
_dittoTask.standardError = self.standardErrorPipe;
@weakify(self);
@@ -63,6 +63,7 @@ const NSInteger SQRLZipArchiverShellTaskFailed = 1;
if (self == nil) return;
[self->_taskTerminated sendNext:@(task.terminationStatus)];
[self.standardErrorPipe.fileHandleForReading closeFile];
};
RACSubject *errorDataChunks = [[RACSubject subject] setNameWithFormat:@"errorDataChunks"];
+56 -20
View File
@@ -15,7 +15,6 @@
#import "NSError+SQRLVerbosityExtensions.h"
#import "RACSignal+SQRLTransactionExtensions.h"
#import "SQRLDirectoryManager.h"
#import "SQRLInstaller.h"
#import "SQRLInstaller+Private.h"
#import "SQRLTerminationListener.h"
@@ -28,6 +27,8 @@
// updating will abort.
static const NSUInteger SQRLShipItMaximumInstallationAttempts = 3;
static NSString * launchSignal = @"___launch___";
// The domain for errors generated here.
static NSString * const SQRLShipItErrorDomain = @"SQRLShipItErrorDomain";
@@ -58,9 +59,7 @@ static RACSignal *waitForTerminationIfNecessary(SQRLShipItRequest *request) {
setNameWithFormat:@"waitForTerminationIfNecessary"];
}
static void installRequest(RACSignal *readRequestSignal, SQRLDirectoryManager *directoryManager) {
NSString *applicationIdentifier = directoryManager.applicationIdentifier;
static void installRequest(RACSignal *readRequestSignal, NSString *applicationIdentifier) {
[[[[[readRequestSignal
flattenMap:^(SQRLShipItRequest *request) {
return waitForTerminationIfNecessary(request);
@@ -75,7 +74,7 @@ static void installRequest(RACSignal *readRequestSignal, SQRLDirectoryManager *d
RACSignal *action;
if (attempt > SQRLShipItMaximumInstallationAttempts) {
action = [[[installer.abortInstallationCommand
action = [[[[installer.abortInstallationCommand
execute:request]
initially:^{
NSLog(@"Too many attempts to install, aborting update");
@@ -86,7 +85,8 @@ static void installRequest(RACSignal *readRequestSignal, SQRLDirectoryManager *d
// Exit successfully so launchd doesn't restart us
// again.
return [RACSignal empty];
}];
}]
concat:[RACSignal return:request]];
} else {
action = [[[[installer.installUpdateCommand
execute:request]
@@ -114,20 +114,44 @@ static void installRequest(RACSignal *readRequestSignal, SQRLDirectoryManager *d
// Launch regardless of whether installation succeeds or fails.
action = [[action
deliverOn:RACScheduler.mainThreadScheduler]
finally:^{
NSURL *bundleURL = request.targetBundleURL;
doNext:^(SQRLShipItRequest *finalRequest) {
NSLog(@"On main thread and launching: %@", finalRequest.targetBundleURL);
NSURL *bundleURL = finalRequest.targetBundleURL;
if (bundleURL == nil) {
NSLog(@"Missing target bundle URL, cannot launch application");
return;
}
NSError *error;
if (![NSWorkspace.sharedWorkspace launchApplicationAtURL:bundleURL options:NSWorkspaceLaunchDefault configuration:nil error:&error]) {
NSLog(@"Could not launch application at %@: %@", bundleURL, error);
return;
}
NSLog(@"Bundle URL is valid");
NSLog(@"Application launched at %@", bundleURL);
NSError *error;
// Temporary workaround, on Big Sur and higher the executable
// using NSWorkspace needs to actually exist on disk, at this point
// this executable no longer exists on disk so we need to launch the
// new one (which should be in the exact same spot) and ask for it
// to launch the new app bundle URL
if (@available(macOS 11.0, *)) {
NSLog(@"Attempting to launch app on 11.0 or higher");
NSString *exe = NSProcessInfo.processInfo.arguments[0];
NSLog(@"Launching new ShipIt at %@ with instructions to launch %@", exe, bundleURL);
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath: exe];
[task setArguments: @[launchSignal, bundleURL.path]];
[task launch];
[task waitUntilExit];
NSLog(@"New ShipIt exited");
} else {
NSLog(@"Attempting to launch app on lower than 11.0");
if (![NSWorkspace.sharedWorkspace launchApplicationAtURL:bundleURL options:NSWorkspaceLaunchDefault configuration:@{} error:&error]) {
NSLog(@"Could not launch application at %@: %@", bundleURL, error);
return;
}
NSLog(@"Application launched at %@", bundleURL);
}
}];
}
@@ -147,17 +171,29 @@ int main(int argc, const char * argv[]) {
NSLog(@"ShipIt quitting");
});
if (argc < 2) {
NSLog(@"Missing launchd job label for ShipIt");
if (argc < 3) {
NSLog(@"Missing launchd job label or state path for ShipIt");
return EXIT_FAILURE;
}
char const *jobLabel = argv[1];
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:@(jobLabel)];
const char *statePath = argv[2];
NSURL *shipItStateURL = [NSURL fileURLWithPath:@(statePath)];
installRequest([SQRLShipItRequest readUsingURL:directoryManager.shipItStateURL], directoryManager);
dispatch_main();
if (strcmp(jobLabel, [launchSignal UTF8String]) == 0) {
NSLog(@"Detected this as a launch request");
NSError *error;
if (![NSWorkspace.sharedWorkspace launchApplicationAtURL:shipItStateURL options:NSWorkspaceLaunchDefault configuration:@{} error:&error]) {
NSLog(@"Could not launch application at %@: %@", shipItStateURL, error);
} else {
NSLog(@"Successfully launched application at %@", shipItStateURL);
}
exit(EXIT_SUCCESS);
} else {
NSLog(@"Detected this as an install request");
installRequest([SQRLShipItRequest readUsingURL:[RACSignal return:shipItStateURL]], @(jobLabel));
dispatch_main();
}
}
return EXIT_SUCCESS;
+1 -1
View File
@@ -9,7 +9,7 @@
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>com.github.${PRODUCT_NAME:rfc1034identifier}</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
+10
View File
@@ -11,6 +11,9 @@
#import <ReactiveCocoa/ReactiveCocoa.h>
#import <Squirrel/Squirrel.h>
/// See https://github.com/Squirrel/Squirrel.Mac/pull/154#issuecomment-151287744
#define SKIP_IF_RUNNING_ON_TRAVIS if (self.runningOnTravis) return;
// The short version string for the `testApplicationBundle`.
extern NSString * const SQRLTestApplicationOriginalShortVersionString;
@@ -21,6 +24,10 @@ extern NSString * const SQRLTestApplicationUpdatedShortVersionString;
// The Info.plist key associated with a bundle's short version string.
extern NSString * const SQRLBundleShortVersionStringKey;
/// The longer timeout we should use for things that take a long time. This is
/// especially needed on slower machines (e.g., Travis CI).
extern const NSTimeInterval SQRLLongTimeout;
@class SQRLCodeSignature;
@class SQRLDirectoryManager;
@class SQRLShipItRequest;
@@ -54,6 +61,9 @@ extern NSString * const SQRLBundleShortVersionStringKey;
// A directory manager for finding URLs that apply to ShipIt.
@property (nonatomic, strong, readonly) SQRLDirectoryManager *shipItDirectoryManager;
/// Are the tests currently being run on Travis?
@property (nonatomic, readonly, assign, getter = isRunningOnTravis) BOOL runningOnTravis;
// Launches a new copy of TestApplication.app from the `testApplicationBundle`.
//
// Invoking this method multiple times will result in multiple running instances
+6 -41
View File
@@ -23,6 +23,8 @@ NSString * const SQRLTestApplicationOriginalShortVersionString = @"1.0";
NSString * const SQRLTestApplicationUpdatedShortVersionString = @"2.1";
NSString * const SQRLBundleShortVersionStringKey = @"CFBundleShortVersionString";
const NSTimeInterval SQRLLongTimeout = 20;
static void SQRLKillAllTestApplications(void) {
// Forcibly kill all copies of the TestApplication that may be running.
NSArray *apps = [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.github.Squirrel.TestApplication"];
@@ -43,24 +45,6 @@ static void SQRLSignalHandler(int sig) {
abort();
}
// Returns an _unlaunched_ task that will follow log files at the given paths,
// then pipe that output through this process.
static NSTask *SQRLTailTaskWithPaths(RACSequence *paths) {
NSPipe *outputPipe = [NSPipe pipe];
NSFileHandle *outputHandle = outputPipe.fileHandleForReading;
outputHandle.readabilityHandler = ^(NSFileHandle *handle) {
NSString *output = [[NSString alloc] initWithData:handle.availableData encoding:NSUTF8StringEncoding];
NSLog(@"\n%@", output);
};
NSTask *task = [[NSTask alloc] init];
task.launchPath = @"/usr/bin/tail";
task.standardOutput = outputPipe;
task.arguments = [[paths startWith:@"-f"] array];
return task;
}
static NSBundle *SQRLTestBundle(void) {
return [NSBundle bundleWithIdentifier:@"com.github.SquirrelTests"];
}
@@ -90,12 +74,6 @@ QuickConfigurationBegin(Fixtures)
].rac_sequence;
}];
RACSequence *paths = [URLs map:^(NSURL *URL) {
return URL.path;
}];
NSTask *readShipIt = SQRLTailTaskWithPaths(paths);
[configuration beforeSuite:^{
signal(SIGILL, &SQRLSignalHandler);
NSSetUncaughtExceptionHandler(&SQRLUncaughtExceptionHandler);
@@ -108,14 +86,6 @@ QuickConfigurationBegin(Fixtures)
NSLog(@"Could not touch log file at %@", URL);
}
}
[readShipIt launch];
NSAssert([readShipIt isRunning], @"Could not start task %@", readShipIt);
}];
[configuration afterSuite:^{
[readShipIt terminate];
}];
// We want to run any enqueued cleanup blocks after _and before_ each spec,
@@ -193,15 +163,6 @@ QuickConfigurationEnd
NSURL *testAppLog = [fixtureURL.URLByDeletingLastPathComponent URLByAppendingPathComponent:@"TestApplication.log"];
[[NSData data] writeToURL:testAppLog atomically:YES];
NSTask *readTestApp = SQRLTailTaskWithPaths([RACSequence return:testAppLog.path]);
[readTestApp launch];
XCTAssertTrue([readTestApp isRunning], @"Could not start task %@ to read %@", readTestApp, testAppLog);
[self addCleanupBlock:^{
[readTestApp terminate];
}];
}
return fixtureURL;
@@ -365,6 +326,10 @@ QuickConfigurationEnd
return [NSURL fileURLWithPath:path isDirectory:YES];
}
- (BOOL)isRunningOnTravis {
return NSProcessInfo.processInfo.environment[@"TRAVIS"] != nil;
}
@end
#pragma clang diagnostic pop
+47
View File
@@ -0,0 +1,47 @@
***
Test updates via release server or cdn-based updates
***
Control test behavior via env variables:
SQUIRREL_TEST_LOCAL_SERVER=True
SQUIRREL_LOCAL_SERVER_URL=http://localhost:8123/update/osx/1.0.1-stable/stable
SQUIRREL_TEST_LOCAL_CDN=True
SQUIRREL_CDN_URL=@"http://localhost/RELEASES.json"
Test Setup:
Create a fake dmg release file
Release Server:
git clone https://github.com/ArekSredzki/electron-release-server.git
refer to their doc, here is a short summary:
- install a local PostgreSQL, any default setup will do it
- edit config/local.js to match your PostgreSQL setup
- npm start
point your browser to the server and upload your release dmg file
CDN:
setup a local webserver such as apache or nginx and copy your dmg file there
create a RELEASE.json file like this:
{
"currentRelease" : "1.0.1",
"releases" : [
{
"version" : "1.0.1",
"updateTo" : {
"pub_date" : "2017-03-09T15:24:55-05:00",
"notes" : "the next release via CDN",
"name" : "TestApplication_1.0.1",
"url" : "http:\/\/localhost/TestApplication_1.0.1.dmg",
"version" : "0.0.145-alpha"
}
}
]
}
+1 -1
View File
@@ -88,7 +88,7 @@ describe(@"main executable changes", ^{
expect(error).notTo(beNil());
expect(error.domain).to(equal(SQRLCodeSignatureErrorDomain));
expect(@(error.code)).to(equal(@(SQRLCodeSignatureErrorCouldNotCreateStaticCode)));
expect(@(error.code)).to(equal(@(SQRLCodeSignatureErrorDidNotPass)));
});
it(@"should fail to verify a bundle with a corrupt executable", ^{
-126
View File
@@ -1,126 +0,0 @@
//
// SQRLDeepCodesignSpec.m
// Squirrel
//
// Created by Keith Duncan on 2013-09-05.
// Copyright (c) 2013 GitHub. All rights reserved.
//
#import <Nimble/Nimble.h>
#import <Quick/Quick.h>
#import <ReactiveCocoa/ReactiveCocoa.h>
#import <Squirrel/Squirrel.h>
#import "QuickSpec+SQRLFixtures.h"
QuickSpecBegin(SQRLDeepCodesign)
NSMutableDictionary * (^environmentSuitableForChildProcess)(void) = ^ {
// Remove environment variables that configure the Obj-C runtime
// Specifically OBJC_DISABLE_GC so that child processes aren't forced to
// adopt the GC preference of the test suite
NSMutableDictionary *environment = [NSProcessInfo.processInfo.environment mutableCopy];
NSSet *objcEnvironmentVariables = [environment keysOfEntriesPassingTest:^(NSString *variable, id obj, BOOL *stop) {
return [variable hasPrefix:@"OBJC"];
}];
[environment removeObjectsForKeys:objcEnvironmentVariables.allObjects];
return environment;
};
NSTask * (^codesignTaskWithArguments)(NSArray *) = ^ (NSArray *arguments) {
NSTask *task = [[NSTask alloc] init];
task.launchPath = @"/usr/bin/xcrun";
task.arguments = [@[ @"codesign" ] arrayByAddingObjectsFromArray:arguments];
task.environment = environmentSuitableForChildProcess();
return task;
};
void (^resignTestApplicationPreserveEverythingButTheRequirements)(void) = ^{
NSURL *testApplicationLocation = self.testApplicationURL;
NSTask *resignCodesignTask = codesignTaskWithArguments(@[
@"--sign", @"-",
@"--force",
@"--verbose=4",
@"--preserve-metadata=identifier,entitlements,resource-rules",
testApplicationLocation.path,
]);
[resignCodesignTask launch];
[resignCodesignTask waitUntilExit];
expect(@(resignCodesignTask.terminationStatus)).to(equal(@0));
};
void (^deepCodesignTestApplication)(void) = ^{
NSURL *testApplicationLocation = self.testApplicationURL;
NSBundle *testsBundle = [NSBundle bundleForClass:self.class];
NSURL *deepCodesignLocation = [testsBundle URLForResource:@"deep-codesign" withExtension:nil];
NSNumber *executable = nil;
NSError *executableError = nil;
BOOL getExecutable = [deepCodesignLocation getResourceValue:&executable forKey:NSURLIsExecutableKey error:&executableError];
expect(@(getExecutable)).to(beTruthy());
expect(@(executable.boolValue)).to(beTruthy());
expect(executableError).to(beNil());
NSTask *deepCodesignTask = [[NSTask alloc] init];
deepCodesignTask.launchPath = deepCodesignLocation.path;
deepCodesignTask.standardError = [NSPipe pipe];
deepCodesignTask.standardOutput = [NSPipe pipe];
NSMutableDictionary *environment = environmentSuitableForChildProcess();
[environment addEntriesFromDictionary:@{
@"CODE_SIGN_IDENTITY": @"-",
@"CONFIGURATION_BUILD_DIR": testApplicationLocation.URLByDeletingLastPathComponent.path,
@"FULL_PRODUCT_NAME": testApplicationLocation.lastPathComponent,
}];
deepCodesignTask.environment = environment;
[deepCodesignTask launch];
[deepCodesignTask waitUntilExit];
expect(@(deepCodesignTask.terminationStatus)).to(equal(@0));
/*
By signing test application's contents, which are covered by test
application's ResourceRules it's signature becomes invalid. Usually,
Xcode would sign the application after deep-codesign has run so the
signature would include the _signed_ child resources which are covered
by test application's ResourceRules
test application is already signed, so we need to manually resign it
deep-codesign preserves the requirements, we can't use it to resign the
root target, the new signing identity wouldn't verify against the
original designated requirement
*/
resignTestApplicationPreserveEverythingButTheRequirements();
};
BOOL (^deepVerify)(void) = ^ BOOL {
NSTask *deepVerifyTask = codesignTaskWithArguments(@[
@"--deep-verify",
@"--verbose=4",
self.testApplicationURL.path
]);
[deepVerifyTask launch];
[deepVerifyTask waitUntilExit];
return deepVerifyTask.terminationStatus == 0;
};
it(@"should deep sign the test application", ^{
deepCodesignTestApplication();
});
xit(@"should deep verify after signing", ^{
expect(@(deepVerify())).to(beFalsy());
deepCodesignTestApplication();
expect(@(deepVerify())).to(beTruthy());
});
QuickSpecEnd
+1 -1
View File
@@ -37,7 +37,7 @@ it(@"should send an Application Support URL", ^{
SQRLDirectoryManager *manager = SQRLDirectoryManager.currentApplicationManager;
NSError *error = nil;
NSURL *appSupportURL = [[manager applicationSupportURL] firstOrDefault:nil success:NULL error:&error];
NSURL *appSupportURL = [[manager storageURL] firstOrDefault:nil success:NULL error:&error];
expect(appSupportURL).notTo(beNil());
expect(error).to(beNil());
+12 -12
View File
@@ -40,7 +40,7 @@ beforeEach(^{
});
it(@"should install an update using ShipIt", ^{
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:NO];
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:NO useUpdateBundleName:NO];
[self installWithRequest:request remote:YES];
@@ -48,7 +48,7 @@ it(@"should install an update using ShipIt", ^{
});
it(@"should install an update in process", ^{
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:NO];
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:NO useUpdateBundleName:NO];
[self installWithRequest:request remote:NO];
@@ -60,7 +60,7 @@ it(@"should install an update and relaunch", ^{
NSArray *apps = [NSRunningApplication runningApplicationsWithBundleIdentifier:bundleIdentifier];
expect(@(apps.count)).to(equal(@0));
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:YES];
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:YES useUpdateBundleName:NO];
[self installWithRequest:request remote:YES];
@@ -72,7 +72,7 @@ it(@"should install an update from another volume", ^{
NSURL *diskImageURL = [self createAndMountDiskImageNamed:@"TestApplication 2.1" fromDirectory:updateURL.URLByDeletingLastPathComponent];
updateURL = [diskImageURL URLByAppendingPathComponent:updateURL.lastPathComponent];
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:NO];
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:NO useUpdateBundleName:NO];
[self installWithRequest:request remote:YES];
@@ -83,12 +83,12 @@ it(@"should install an update to another volume", ^{
NSURL *diskImageURL = [self createAndMountDiskImageNamed:@"TestApplication" fromDirectory:self.testApplicationURL.URLByDeletingLastPathComponent];
NSURL *targetURL = [diskImageURL URLByAppendingPathComponent:self.testApplicationURL.lastPathComponent];
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:targetURL bundleIdentifier:nil launchAfterInstallation:NO];
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:targetURL bundleIdentifier:nil launchAfterInstallation:NO useUpdateBundleName:NO];
[self installWithRequest:request remote:YES];
NSURL *plistURL = [targetURL URLByAppendingPathComponent:@"Contents/Info.plist"];
expect([NSDictionary dictionaryWithContentsOfURL:plistURL][SQRLBundleShortVersionStringKey]).toEventually(equal(SQRLTestApplicationUpdatedShortVersionString));
expect([NSDictionary dictionaryWithContentsOfURL:plistURL][SQRLBundleShortVersionStringKey]).withTimeout(SQRLLongTimeout).toEventually(equal(SQRLTestApplicationUpdatedShortVersionString));
});
describe(@"with backup restoration", ^{
@@ -118,7 +118,7 @@ describe(@"with backup restoration", ^{
});
it(@"should not install an update after too many attempts", ^{
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:targetURL bundleIdentifier:nil launchAfterInstallation:NO];
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:targetURL bundleIdentifier:nil launchAfterInstallation:NO useUpdateBundleName:NO];
[self installWithRequest:request remote:YES];
__block NSError *error;
@@ -129,11 +129,11 @@ describe(@"with backup restoration", ^{
});
it(@"should relaunch even after failing to install an update", ^{
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:targetURL bundleIdentifier:nil launchAfterInstallation:YES];
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:targetURL bundleIdentifier:nil launchAfterInstallation:YES useUpdateBundleName:NO];
[self installWithRequest:request remote:YES];
expect(@([NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.github.Squirrel.TestApplication"].count)).toEventually(equal(@1));
__block NSError *error;
expect(@([[self.testApplicationSignature verifyBundleAtURL:targetURL] waitUntilCompleted:&error])).to(beTruthy());
expect(error).to(beNil());
@@ -149,7 +149,7 @@ it(@"should disallow writing the updated application except by the owner", ^{
expect(@(modeOfURL(updateURL))).to(equal(@0777));
expect(@(modeOfURL([updateURL URLByAppendingPathComponent:@"Contents/MacOS/TestApplication"]))).to(equal(@0777));
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:NO];
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:NO useUpdateBundleName:NO];
[self installWithRequest:request remote:YES];
@@ -165,7 +165,7 @@ describe(@"signal handling", ^{
void (^verifyUpdate)(void) = ^{
// Wait up to the launchd throttle interval, then verify that ShipIt
// relaunched and finished installing the update.
expect(self.testApplicationBundleVersion).withTimeout(5).toEventually(equal(SQRLTestApplicationUpdatedShortVersionString));
expect(self.testApplicationBundleVersion).withTimeout(SQRLLongTimeout).toEventually(equal(SQRLTestApplicationUpdatedShortVersionString));
NSError *error;
BOOL success = [[self.testApplicationSignature verifyBundleAtURL:targetURL] waitUntilCompleted:&error];
@@ -178,7 +178,7 @@ describe(@"signal handling", ^{
// accessing the property.
targetURL = self.testApplicationURL;
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:NO];
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:NO useUpdateBundleName:NO];
[self installWithRequest:request remote:YES];
+1 -1
View File
@@ -25,7 +25,7 @@ beforeEach(^{
directoryManager = SQRLDirectoryManager.currentApplicationManager;
NSURL *updateURL = [self createTestApplicationUpdate];
request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:NO];
request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:NO useUpdateBundleName:NO];
expect(request).notTo(beNil());
expect(request.targetBundleURL).to(equal(self.testApplicationURL));
@@ -34,6 +34,8 @@ it(@"should complete immediately when the app is not running", ^{
});
it(@"should wait until one instance terminates", ^{
SKIP_IF_RUNNING_ON_TRAVIS
NSRunningApplication *app = [self launchTestApplicationWithEnvironment:nil];
__block NSRunningApplication *observedApp = nil;
@@ -53,6 +55,8 @@ it(@"should wait until one instance terminates", ^{
});
it(@"should wait until multiple instances terminate", ^{
SKIP_IF_RUNNING_ON_TRAVIS
NSRunningApplication *app1 = [self launchTestApplicationWithEnvironment:nil];
NSRunningApplication *app2 = [self launchTestApplicationWithEnvironment:nil];
+2 -2
View File
@@ -45,12 +45,12 @@ it(@"should validate release name and notes", ^{
it(@"should parse Central style dates", ^{
SQRLUpdate *update = [MTLJSONAdapter modelOfClass:SQRLUpdate.class fromJSONDictionary:@{ @"url": @"http://example.com/update", @"pub_date": @"Tue Sep 17 10:24:27 -0700 2013" } error:NULL];
expect(update.releaseDate).notTo(beNil());
expect(update.releaseDate).to(equal([NSDate dateWithTimeIntervalSince1970:1379438667]));
});
it(@"should parse ISO 8601 dates", ^{
SQRLUpdate *update = [MTLJSONAdapter modelOfClass:SQRLUpdate.class fromJSONDictionary:@{ @"url": @"http://example.com/update", @"pub_date": @"2013-09-18T13:17:07+01:00" } error:NULL];
expect(update.releaseDate).notTo(beNil());
expect(update.releaseDate).to(equal([NSDate dateWithTimeIntervalSince1970:1379506627]));
});
QuickSpecEnd
+172 -10
View File
@@ -12,12 +12,31 @@
#import <Squirrel/Squirrel.h>
#import "SQRLDirectoryManager.h"
#import "SQRLUpdater.h"
#import "SQRLZipArchiver.h"
#import "OHHTTPStubs/OHHTTPStubs.h"
#import "QuickSpec+SQRLFixtures.h"
#import "SQRLTestUpdate.h"
#import "TestAppConstants.h"
#import <objc/objc-class.h>
//! force Updater to believe we are not on readonly, to continue downloading the release
bool isRunningOnReadOnlyVolumeImp(id self, SEL _cmd)
{
return false;
}
//! we don't want updateFromJSONData to be executed. It is enough to know if it was called
bool updateFromJSONDataIsCalled = false;
RACSignal * updateFromJSONDataImp(id self, SEL _cmd, NSData * data)
{
NSString* str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"updateFromJSONDataImp called with %@", str);
updateFromJSONDataIsCalled = true;
return false;
}
QuickSpecBegin(SQRLUpdaterSpec)
@@ -59,6 +78,134 @@ beforeEach(^{
JSONURL = [self.temporaryDirectoryURL URLByAppendingPathComponent:@"update.json"];
});
describe(@"checkForUpdatesCommand", ^{
/**
control test behavior via env variables:
SQUIRREL_TEST_LOCAL_SERVER=True
SQUIRREL_LOCAL_SERVER_URL=http://localhost:8123/update/osx/1.0.1-stable/stable
SQUIRREL_TEST_LOCAL_CDN=True
SQUIRREL_CDN_URL=@"http://localhost/RELEASES.json"
*/
OHHTTPStubs *stubsJson = [OHHTTPStubs shouldStubRequestsPassingTest:^(NSURLRequest *request) {
return [request.URL.absoluteString isEqualToString:@"http://localhost/RELEASES.json?method=Json"];
} withStubResponse:^(NSURLRequest *request) {
NSDictionary *newReleaseJSON = @{
@"version": @"0.0.145",
@"name": @"my-stub-release",
@"notes": @"mock release for automated tests, json",
@"pub_date": @"2017-03-09T15:24:55-05:00",
@"url": @"http://localhost/myapp-0.0.145.zip"
};
NSError * err;
NSData * jsonData = [NSJSONSerialization dataWithJSONObject:newReleaseJSON options:0 error:&err];
return [OHHTTPStubsResponse responseWithData:jsonData statusCode:200 responseTime:0 headers:nil];
}];
OHHTTPStubs *stubsReleaseServer = [OHHTTPStubs shouldStubRequestsPassingTest:^(NSURLRequest *request) {
return [request.URL.absoluteString isEqualToString:@"http://localhost:8123/update/osx/1.0.1-stable/stable?method=ReleaseServer"];
} withStubResponse:^(NSURLRequest *request) {
NSDictionary *newReleaseJSON = @{
@"version": @"0.0.145",
@"name": @"my-stub-release",
@"notes": @"mock release for automated tests, release server",
@"pub_date": @"2017-03-09T15:24:55-05:00",
@"url": @"http://localmyappng-0.0.145.zip"
};
NSError * err;
NSData * jsonData = [NSJSONSerialization dataWithJSONObject:newReleaseJSON options:0 error:&err];
return [OHHTTPStubsResponse responseWithData:jsonData statusCode:200 responseTime:0 headers:nil];
}];
[self addCleanupBlock:^{
[OHHTTPStubs removeRequestHandler:stubsJson];
[OHHTTPStubs removeRequestHandler:stubsReleaseServer];
}];
BOOL testLocalServer = [[[[NSProcessInfo processInfo]environment]objectForKey:@"SQUIRREL_TEST_LOCAL_SERVER"] boolValue];
NSString* localServerURL = [[[NSProcessInfo processInfo]environment]objectForKey:@"SQUIRREL_LOCAL_SERVER_URL"];
if(!localServerURL) {
localServerURL = @"http://localhost:8123/update/osx/1.0.1-stable/stable?method=ReleaseServer";
}
BOOL testLocalCdn = [[[[NSProcessInfo processInfo]environment]objectForKey:@"SQUIRREL_TEST_LOCAL_CDN"] boolValue];
NSString* localCdnURL = [[[NSProcessInfo processInfo]environment]objectForKey:@"SQUIRREL_CDN_URL"];
if(!localCdnURL) {
localCdnURL = @"http://localhost/RELEASES.json?method=Json";
}
NSLog(@"testLocalServer %d %@", testLocalServer, localServerURL);
NSLog(@"testLocalCdn %d %@", testLocalCdn, localCdnURL);
__block SQRLUpdater *updater = nil;
__block NSURLRequest *localRequest = nil;
it(@"Squirrel should work with a release server", ^{
if(testLocalServer) {
updateFromJSONDataIsCalled = false;
//! setup the updater
localRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:localServerURL]];
updater = [[SQRLUpdater alloc] initWithUpdateRequest:localRequest];
//! replace updateFromJSONData() and isRunningOnReadOnlyVolume() methods
method_setImplementation(class_getInstanceMethod([SQRLUpdater class]
, @selector(updateFromJSONData:))
, (IMP) updateFromJSONDataImp);
method_setImplementation(class_getInstanceMethod([SQRLUpdater class]
, @selector(isRunningOnReadOnlyVolume))
, (IMP) isRunningOnReadOnlyVolumeImp);
NSError *error = nil;
BOOL result = [[updater.checkForUpdatesCommand execute:nil] asynchronouslyWaitUntilCompleted:&error];
//! now check the results
expect((int)updater.state).toEventually(equal((int)SQRLUpdaterStateIdle));
expect( (BOOL) updateFromJSONDataIsCalled ).to(beTrue());
expect( (BOOL) result ).to(beTrue());
}
});
it(@"Squirrel should work with a CDN", ^{
if(testLocalCdn) {
updateFromJSONDataIsCalled = false;
//! setup the updater
localRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:localCdnURL]];
updater = [[SQRLUpdater alloc] initWithUpdateRequest:localRequest];
//! replace updateFromJSONData() and isRunningOnReadOnlyVolume() methods
method_setImplementation(class_getInstanceMethod([SQRLUpdater class]
, @selector(updateFromJSONData:))
, (IMP) updateFromJSONDataImp);
method_setImplementation(class_getInstanceMethod([SQRLUpdater class]
, @selector(isRunningOnReadOnlyVolume))
, (IMP) isRunningOnReadOnlyVolumeImp);
NSError *error = nil;
BOOL result = [[updater.checkForUpdatesCommand execute:nil] asynchronouslyWaitUntilCompleted:&error];
//! now check the results
expect((int)updater.state).toEventually(equal((int)SQRLUpdaterStateIdle));
expect( (BOOL) updateFromJSONDataIsCalled ).to(beTrue());
expect( (BOOL) result ).to(beTrue());
}
});
});
describe(@"updating", ^{
__block NSURL *updateURL;
@@ -67,6 +214,8 @@ describe(@"updating", ^{
});
it(@"should use the application's bundled version of Squirrel and update in-place", ^{
SKIP_IF_RUNNING_ON_TRAVIS
NSError *error = nil;
SQRLTestUpdate *update = [SQRLTestUpdate modelWithDictionary:@{
@"updateURL": zipUpdate(updateURL),
@@ -78,11 +227,13 @@ describe(@"updating", ^{
writeUpdate(update);
NSRunningApplication *app = launchWithEnvironment(nil);
expect(@(app.terminated)).toEventually(beTruthy());
expect(@(app.terminated)).withTimeout(SQRLLongTimeout).toEventually(beTruthy());
expect(self.testApplicationBundleVersion).toEventually(equal(SQRLTestApplicationUpdatedShortVersionString));
});
it(@"should not install a corrupt update", ^{
SKIP_IF_RUNNING_ON_TRAVIS
NSURL *codeSignatureURL = [updateURL URLByAppendingPathComponent:@"Contents/_CodeSignature"];
expect(@([NSFileManager.defaultManager removeItemAtURL:codeSignatureURL error:NULL])).to(beTruthy());
@@ -94,7 +245,7 @@ describe(@"updating", ^{
writeUpdate(update);
NSRunningApplication *app = launchWithEnvironment(nil);
expect(@(app.terminated)).withTimeout(5).toEventually(beTruthy());
expect(@(app.terminated)).withTimeout(SQRLLongTimeout).toEventually(beTruthy());
// Give the update some time to finish installing.
[NSThread sleepForTimeInterval:0.2];
@@ -102,6 +253,8 @@ describe(@"updating", ^{
});
it(@"should update to the most recently enqueued job", ^{
SKIP_IF_RUNNING_ON_TRAVIS
SQRLTestUpdate *update = [SQRLTestUpdate modelWithDictionary:@{
@"updateURL": zipUpdate(self.testApplicationURL)
} error:NULL];
@@ -125,11 +278,13 @@ describe(@"updating", ^{
writeUpdate(update);
expect(@(app.terminated)).withTimeout(5).toEventually(beTruthy());
expect(@(app.terminated)).withTimeout(SQRLLongTimeout).toEventually(beTruthy());
expect(self.testApplicationBundleVersion).toEventually(equal(SQRLTestApplicationUpdatedShortVersionString));
});
it(@"should use the application's bundled version of Squirrel and update in-place after a significant delay", ^{
SKIP_IF_RUNNING_ON_TRAVIS
SQRLTestUpdate *update = [SQRLTestUpdate modelWithDictionary:@{
@"updateURL": zipUpdate(updateURL),
@"final": @YES
@@ -140,7 +295,7 @@ describe(@"updating", ^{
NSTimeInterval delay = 15;
NSRunningApplication *app = launchWithEnvironment(@{ @"SQRLUpdateDelay": [NSString stringWithFormat:@"%f", delay] });
expect(@(app.terminated)).withTimeout(delay + 5).toEventually(beTruthy());
expect(@(app.terminated)).withTimeout(delay + SQRLLongTimeout).toEventually(beTruthy());
expect(self.testApplicationBundleVersion).toEventually(equal(SQRLTestApplicationUpdatedShortVersionString));
});
@@ -151,7 +306,7 @@ describe(@"updating", ^{
beforeEach(^{
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:@"com.github.Squirrel.TestApplication.ShipIt"];
appSupportURL = [[directoryManager applicationSupportURL] first];
appSupportURL = [[directoryManager storageURL] first];
expect(appSupportURL).notTo(beNil());
updateDirectoryURLs = [[RACSignal
@@ -167,6 +322,8 @@ describe(@"updating", ^{
});
it(@"should remove downloaded archives after updating", ^{
SKIP_IF_RUNNING_ON_TRAVIS
NSError *error = nil;
SQRLTestUpdate *update = [SQRLTestUpdate modelWithDictionary:@{
@"updateURL": zipUpdate(updateURL),
@@ -178,8 +335,9 @@ describe(@"updating", ^{
writeUpdate(update);
NSRunningApplication *app = launchWithEnvironment(nil);
expect(@(app.terminated)).withTimeout(5).toEventually(beTruthy());
NSRunningApplication *app = launchWithEnvironment(@{ @"SQRLUpdateRequestCount": @2 });
expect([updateDirectoryURLs toArray]).toEventuallyNot(equal(@[]));
expect(@(app.terminated)).withTimeout(SQRLLongTimeout).toEventually(beTruthy());
expect(self.testApplicationBundleVersion).toEventually(equal(SQRLTestApplicationUpdatedShortVersionString));
expect([updateDirectoryURLs toArray]).toEventually(equal(@[]));
@@ -242,6 +400,8 @@ static RACSignal * (^stateNotificationListener)(void) = ^ {
describe(@"state", ^{
it(@"should transition through idle, checking and idle, when there is no update", ^{
SKIP_IF_RUNNING_ON_TRAVIS
NSMutableArray *states = [NSMutableArray array];
[stateNotificationListener() subscribeNext:^(NSNumber *state) {
[states addObject:state];
@@ -256,10 +416,12 @@ describe(@"state", ^{
];
expect(states).toEventually(equal(expectedStates));
expect(@(testApplication.terminated)).withTimeout(5).toEventually(beTruthy());
expect(@(testApplication.terminated)).withTimeout(SQRLLongTimeout).toEventually(beTruthy());
});
it(@"should transition through idle, checking, downloading and awaiting relaunch, when there is an update", ^{
SKIP_IF_RUNNING_ON_TRAVIS
NSMutableArray *states = [NSMutableArray array];
[stateNotificationListener() subscribeNext:^(NSNumber *state) {
[states addObject:state];
@@ -283,9 +445,9 @@ describe(@"state", ^{
@(SQRLUpdaterStateDownloadingUpdate),
@(SQRLUpdaterStateAwaitingRelaunch),
];
expect(states).toEventually(equal(expectedStates));
expect(states).withTimeout(SQRLLongTimeout).toEventually(equal(expectedStates));
expect(@(testApplication.terminated)).withTimeout(5).toEventually(beTruthy());
expect(@(testApplication.terminated)).withTimeout(SQRLLongTimeout).toEventually(beTruthy());
});
});
+1 -1
View File
@@ -7,7 +7,7 @@
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>com.github.${PRODUCT_NAME:rfc1034identifier}</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
@@ -0,0 +1 @@
Versions/Current/Headers
@@ -0,0 +1 @@
Versions/Current/Mantle
@@ -0,0 +1 @@
Versions/Current/Modules
@@ -0,0 +1 @@
Versions/Current/Resources
@@ -0,0 +1,172 @@
//
// MTLJSONAdapter.h
// Mantle
//
// Created by Justin Spahr-Summers on 2013-02-12.
// Copyright (c) 2013 GitHub. All rights reserved.
//
#import <Foundation/Foundation.h>
@class MTLModel;
// A MTLModel object that supports being parsed from and serialized to JSON.
@protocol MTLJSONSerializing
@required
// Specifies how to map property keys to different key paths in JSON.
//
// Subclasses overriding this method should combine their values with those of
// `super`.
//
// Any property keys not present in the dictionary are assumed to match the JSON
// key that should be used. Any keys associated with NSNull will not participate
// in JSON serialization.
//
// Returns a dictionary mapping property keys to JSON key paths (as strings) or
// NSNull values.
+ (NSDictionary *)JSONKeyPathsByPropertyKey;
@optional
// Specifies how to convert a JSON value to the given property key. If
// reversible, the transformer will also be used to convert the property value
// back to JSON.
//
// If the receiver implements a `+<key>JSONTransformer` method, MTLJSONAdapter
// will use the result of that method instead.
//
// Returns a value transformer, or nil if no transformation should be performed.
+ (NSValueTransformer *)JSONTransformerForKey:(NSString *)key;
// Overridden to parse the receiver as a different class, based on information
// in the provided dictionary.
//
// This is mostly useful for class clusters, where the abstract base class would
// be passed into -[MTLJSONAdapter initWithJSONDictionary:modelClass:], but
// a subclass should be instantiated instead.
//
// JSONDictionary - The JSON dictionary that will be parsed.
//
// Returns the class that should be parsed (which may be the receiver), or nil
// to abort parsing (e.g., if the data is invalid).
+ (Class)classForParsingJSONDictionary:(NSDictionary *)JSONDictionary;
@end
// The domain for errors originating from MTLJSONAdapter.
extern NSString * const MTLJSONAdapterErrorDomain;
// +classForParsingJSONDictionary: returned nil for the given dictionary.
extern const NSInteger MTLJSONAdapterErrorNoClassFound;
// The provided JSONDictionary is not valid.
extern const NSInteger MTLJSONAdapterErrorInvalidJSONDictionary;
// The model's implementation of +JSONKeyPathsByPropertyKey included a key which
// does not actually exist in +propertyKeys.
extern const NSInteger MTLJSONAdapterErrorInvalidJSONMapping;
// Converts a MTLModel object to and from a JSON dictionary.
@interface MTLJSONAdapter : NSObject
// The model object that the receiver was initialized with, or that the receiver
// parsed from a JSON dictionary.
@property (nonatomic, strong, readonly) MTLModel<MTLJSONSerializing> *model;
// Attempts to parse a JSON dictionary into a model object.
//
// modelClass - The MTLModel subclass to attempt to parse from the JSON.
// This class must conform to <MTLJSONSerializing>. This
// argument must not be nil.
// JSONDictionary - A dictionary representing JSON data. This should match the
// format returned by NSJSONSerialization. If this argument is
// nil, the method returns nil.
// error - If not NULL, this may be set to an error that occurs during
// parsing or initializing an instance of `modelClass`.
//
// Returns an instance of `modelClass` upon success, or nil if a parsing error
// occurred.
+ (id)modelOfClass:(Class)modelClass fromJSONDictionary:(NSDictionary *)JSONDictionary error:(NSError **)error;
// Attempts to parse an array of JSON dictionary objects into a model objects
// of a specific class.
//
// modelClass - The MTLModel subclass to attempt to parse from the JSON. This
// class must conform to <MTLJSONSerializing>. This argument must
// not be nil.
// JSONArray - A array of dictionaries representing JSON data. This should
// match the format returned by NSJSONSerialization. If this
// argument is nil, the method returns nil.
// error - If not NULL, this may be set to an error that occurs during
// parsing or initializing an any of the instances of
// `modelClass`.
//
// Returns an array of `modelClass` instances upon success, or nil if a parsing
// error occurred.
+ (NSArray *)modelsOfClass:(Class)modelClass fromJSONArray:(NSArray *)JSONArray error:(NSError **)error;
// Converts a model into a JSON representation.
//
// model - The model to use for JSON serialization. This argument must not be
// nil.
//
// Returns a JSON dictionary, or nil if a serialization error occurred.
+ (NSDictionary *)JSONDictionaryFromModel:(MTLModel<MTLJSONSerializing> *)model;
// Converts a array of models into a JSON representation.
//
// models - The array of models to use for JSON serialization. This argument
// must not be nil.
//
// Returns a JSON array, or nil if a serialization error occurred for any
// model.
+ (NSArray *)JSONArrayFromModels:(NSArray *)models;
// Initializes the receiver by attempting to parse a JSON dictionary into
// a model object.
//
// JSONDictionary - A dictionary representing JSON data. This should match the
// format returned by NSJSONSerialization. If this argument is
// nil, the method returns nil and an error with code
// MTLJSONAdapterErrorInvalidJSONDictionary.
// modelClass - The MTLModel subclass to attempt to parse from the JSON.
// This class must conform to <MTLJSONSerializing>. This
// argument must not be nil.
// error - If not NULL, this may be set to an error that occurs during
// parsing or initializing an instance of `modelClass`.
//
// Returns an initialized adapter upon success, or nil if a parsing error
// occurred.
- (id)initWithJSONDictionary:(NSDictionary *)JSONDictionary modelClass:(Class)modelClass error:(NSError **)error;
// Initializes the receiver with an existing model.
//
// model - The model to use for JSON serialization. This argument must not be
// nil.
- (id)initWithModel:(MTLModel<MTLJSONSerializing> *)model;
// Serializes the receiver's `model` into JSON.
//
// Returns a JSON dictionary, or nil if a serialization error occurred.
- (NSDictionary *)JSONDictionary;
// Looks up the JSON key path in the model's +propertyKeys.
//
// Subclasses may override this method to customize the adapter's seralizing
// behavior. You should not call this method directly.
//
// key - The property key to retrieve the corresponding JSON key path for. This
// argument must not be nil.
//
// Returns a key path to use, or nil to omit the property from JSON.
- (NSString *)JSONKeyPathForPropertyKey:(NSString *)key;
@end
@interface MTLJSONAdapter (Deprecated)
+ (id)modelOfClass:(Class)modelClass fromJSONDictionary:(NSDictionary *)JSONDictionary __attribute__((deprecated("Replaced by +modelOfClass:fromJSONDictionary:error:")));
- (id)initWithJSONDictionary:(NSDictionary *)JSONDictionary modelClass:(Class)modelClass __attribute__((deprecated("Replaced by -initWithJSONDictionary:modelClass:error:")));
@end
@@ -0,0 +1,215 @@
//
// MTLManagedObjectAdapter.h
// Mantle
//
// Created by Justin Spahr-Summers on 2013-03-29.
// Copyright (c) 2013 GitHub. All rights reserved.
//
#import <CoreData/CoreData.h>
@class MTLModel;
// A MTLModel object that supports being serialized to and from Core Data as an
// NSManagedObject.
@protocol MTLManagedObjectSerializing
@required
// The name of the Core Data entity that the receiver serializes to and
// deserializes from.
//
// This method must not return nil.
+ (NSString *)managedObjectEntityName;
// Specifies how to map property keys to different keys on the receiver's
// +managedObjectEntity.
//
// Entity attributes will be mapped to and from the receiver's properties using
// +entityAttributeTransformerForKey:. Entity relationships will be mapped to
// and from MTLModel objects using +relationshipModelClassesByPropertyKey.
// Fetched properties are not supported.
//
// Subclasses overriding this method should combine their values with those of
// `super`.
//
// Any property keys not present in the dictionary are assumed to match the
// entity key that should be used. Any keys associated with NSNull will not
// participate in managed object serialization.
//
// Returns a dictionary mapping property keys to entity keys (as strings) or
// NSNull values.
+ (NSDictionary *)managedObjectKeysByPropertyKey;
@optional
// Specifies a set of property keys used by the adapter to check for an already
// existing managed object when converting the MTLModel to its related
// NSManagedObject.
//
// The adapter will first map any keys provided by this method to the correct
// keys in managedObjectKeysByPropertyKey.
//
// The adapter will then perform a fetch request in the provided context for a
// managed object that matches the MTLModel's managedObjectEntityName and has
// equal values set for the property keys on the MTLModel.
//
// The managed object returned by the fetch request will then be set with all
// values from the MTLModel that the managed object is being converted from.
//
// If a property value of our MTLModel is yet another MTLModel which needs to be
// converted to a managed object, the class for that MTLModel can also implement
// this method to perform its own uniqing.
//
// For example:
// 1. An MTLModel subclass has id_number = 10.
// 2. An NSManagedObject accessible to the adapter's context has idnumber = 10.
// 3. managedObjectKeysByPropertyKey returns @{@"id_number" : @"idnumber"}
// 4. propertyKeysForManagedObjectUniquing returns
// [NSSet setWithObject:@"id_number"];
// 5. Then our fetch request may return this managed object (or another managed
// object with idnumber = 10).
//
// NOTE: If multiple managed objects follow the same uniquing criteria only one
// of them will be set with our MTLModel's values.
+ (NSSet *)propertyKeysForManagedObjectUniquing;
// Specifies how to convert the given property key to a managed object
// attribute. If reversible, the transformer will also be used to convert the
// managed object attribute back to the property.
//
// If the receiver implements a `+<key>EntityAttributeTransformer` method,
// MTLManagedObjectAdapter will use the result of that method instead.
//
// Returns a value transformer, or nil if no transformation should be performed.
+ (NSValueTransformer *)entityAttributeTransformerForKey:(NSString *)key;
// Specifies the MTLModel subclasses that should be deserialized to the
// receiver's property keys when a property key corresponds to an entity
// relationship.
//
// In other words, the dictionary returned by this method is used to decode
// managed object relationships into MTLModels (or NSArrays or NSSets thereof)
// set on the receiver.
//
// If a property key is omitted from the returned dictionary, but present in
// +managedObjectKeysByPropertyKey, and the receiver's +managedObjectEntity has
// a relationship at the corresponding key, an exception will be thrown during
// deserialization.
//
// Subclasses overriding this method should combine their values with those of
// `super`.
//
// Returns a dictionary mapping property keys to the Class objects that should
// be used.
+ (NSDictionary *)relationshipModelClassesByPropertyKey;
// Overridden to deserialize a different class instead of the receiver, based on
// information in the provided object.
//
// This is mostly useful for class clusters, where the abstract base class would
// be passed into +[MTLManagedObjectAdapter
// modelOfClass:fromManagedObject:error:], but a subclass should be instantiated
// instead.
//
// managedObject - The object that will be deserialized.
//
// Returns the class that should be instantiated (which may be the receiver), or
// nil to abort parsing (e.g., if the data is invalid).
+ (Class)classForDeserializingManagedObject:(NSManagedObject *)managedObject;
// Overriden when merging the value of the given key on the receiver with the
// value of the same key from the given `NSManagedObject` requires custom
// handling.
//
// By default, this method is not implemented, and precedence will be given to
// the value of the receiving model implicitly.
//
// When implemented, this method is called when an existing `NSManagedObject`
// is found for the receiving model, before updating the `NSManagedObject`'s
// properties.
//
// When implementing, you should use `+managedObjectKeysByPropertyKey` to map
// the given `key` to the appropriate `NSManagedObject` property.
- (void)mergeValueForKey:(NSString *)key fromManagedObject:(NSManagedObject *)managedObject;
// Overriden when merging values on the receiver with the given
// `NSManagedObject` requires custom handling.
//
// By default, this method is not implemented, and precedence will be given to
// the values of the receiving model implicitly.
//
// When implemented, this method is called when an existing `NSManagedObject`
// is found for the receiving model, before updating the `NSManagedObject`'s
// properties.
//
// When implementing, you should use `+managedObjectKeysByPropertyKey` to map
// the given `key` to the appropriate `NSManagedObject` property.
//
// If you have also implemented `mergeValueForKey:fromManagedObject:` you have
// to make sure to call `mergeValueForKey:fromManagedObject:` from this method
// when appropriate.
- (void)mergeValuesForKeysFromManagedObject:(NSManagedObject *)managedObject;
@end
// The domain for errors originating from MTLManagedObjectAdapter.
extern NSString * const MTLManagedObjectAdapterErrorDomain;
// +classForDeserializingManagedObject: returned nil for the given object.
extern const NSInteger MTLManagedObjectAdapterErrorNoClassFound;
// An NSManagedObject failed to initialize.
extern const NSInteger MTLManagedObjectAdapterErrorInitializationFailed;
// The managed object key specified by +managedObjectKeysByPropertyKey does not
// exist in the NSEntityDescription.
extern const NSInteger MTLManagedObjectAdapterErrorInvalidManagedObjectKey;
// The managed object property specified has a type that isn't supported by
// MTLManagedObjectAdapter.
extern const NSInteger MTLManagedObjectAdapterErrorUnsupportedManagedObjectPropertyType;
// The fetch request to find an existing managed object based on
// `+propertyKeysForManagedObjectUniquing` failed.
extern const NSInteger MTLManagedObjectAdapterErrorUniqueFetchRequestFailed;
// A MTLModel property cannot be serialized to or deserialized from an
// NSManagedObject relationship.
//
// For a to-one relationship, this means that the property does not contain
// a MTLModel, or the MTLModel does not conform to <MTLManagedObjectSerializing>.
//
// For a to-many relationship, this means that the property does not contain an
// NSArray or NSSet of MTLModel<MTLManagedObjectSerializing> instances.
extern const NSInteger MTLManagedObjectAdapterErrorUnsupportedRelationshipClass;
// The model's implementation of +managedObjectKeysByPropertyKey included a key
// which does not actually exist in +propertyKeys.
extern const NSInteger MTLManagedObjectAdapterErrorInvalidManagedObjectMapping;
// Converts a MTLModel object to and from an NSManagedObject.
@interface MTLManagedObjectAdapter : NSObject
// Attempts to deserialize an NSManagedObject into a MTLModel object.
//
// modelClass - The MTLModel subclass to return. This class must conform to
// <MTLManagedObjectSerializing>. This argument must not be nil.
// managedObject - The managed object to deserialize. If this argument is nil,
// the method returns nil.
// error - If not NULL, this may be set to an error that occurs during
// deserialization or initializing an instance of `modelClass`.
//
// Returns an instance of `modelClass` upon success, or nil if an error
// occurred.
+ (id)modelOfClass:(Class)modelClass fromManagedObject:(NSManagedObject *)managedObject error:(NSError **)error;
// Serializes a MTLModel into an NSManagedObject.
//
// model - The model object to serialize. This argument must not be nil.
// context - The context into which to insert the created managed object. This
// argument must not be nil.
// error - If not NULL, this may be set to an error that occurs during
// serialization or insertion.
+ (id)managedObjectFromModel:(MTLModel<MTLManagedObjectSerializing> *)model insertingIntoContext:(NSManagedObjectContext *)context error:(NSError **)error;
@end
@@ -0,0 +1,128 @@
//
// MTLModel+NSCoding.h
// Mantle
//
// Created by Justin Spahr-Summers on 2013-02-12.
// Copyright (c) 2013 GitHub. All rights reserved.
//
#import "MTLModel.h"
// Defines how a MTLModel property key should be encoded into an archive.
//
// MTLModelEncodingBehaviorExcluded - The property should never be encoded.
// MTLModelEncodingBehaviorUnconditional - The property should always be
// encoded.
// MTLModelEncodingBehaviorConditional - The object should be encoded only
// if unconditionally encoded elsewhere.
// This should only be used for object
// properties.
typedef enum : NSUInteger {
MTLModelEncodingBehaviorExcluded = 0,
MTLModelEncodingBehaviorUnconditional,
MTLModelEncodingBehaviorConditional,
} MTLModelEncodingBehavior;
// Implements default archiving and unarchiving behaviors for MTLModel.
@interface MTLModel (NSCoding) <NSCoding>
// Initializes the receiver from an archive.
//
// This will decode the original +modelVersion of the archived object, then
// invoke -decodeValueForKey:withCoder:modelVersion: for each of the receiver's
// +propertyKeys.
//
// Returns an initialized model object, or nil if a decoding error occurred.
- (id)initWithCoder:(NSCoder *)coder;
// Archives the receiver using the given coder.
//
// This will encode the receiver's +modelVersion, then the receiver's properties
// according to the behaviors specified in +encodingBehaviorsByPropertyKey.
- (void)encodeWithCoder:(NSCoder *)coder;
// Determines how the +propertyKeys of the class are encoded into an archive.
// The values of this dictionary should be boxed MTLModelEncodingBehavior
// values.
//
// Any keys not present in the dictionary will be excluded from the archive.
//
// Subclasses overriding this method should combine their values with those of
// `super`.
//
// Returns a dictionary mapping the receiver's +propertyKeys to default encoding
// behaviors. If a property is an object with `weak` semantics, the default
// behavior is MTLModelEncodingBehaviorConditional; otherwise, the default is
// MTLModelEncodingBehaviorUnconditional.
+ (NSDictionary *)encodingBehaviorsByPropertyKey;
// Determines the classes that are allowed to be decoded for each of the
// receiver's properties when using <NSSecureCoding>. The values of this
// dictionary should be NSArrays of Class objects.
//
// If any encodable keys (as determined by +encodingBehaviorsByPropertyKey) are
// not present in the dictionary, an exception will be thrown during secure
// encoding or decoding.
//
// Subclasses overriding this method should combine their values with those of
// `super`.
//
// Returns a dictionary mapping the receiver's encodable keys (as determined by
// +encodingBehaviorsByPropertyKey) to default allowed classes, based on the
// type that each property is declared as. If type of an encodable property
// cannot be determined (e.g., it is declared as `id`), it will be omitted from
// the dictionary, and subclasses must provide a valid value to prevent an
// exception being thrown during encoding/decoding.
+ (NSDictionary *)allowedSecureCodingClassesByPropertyKey;
// Decodes the value of the given property key from an archive.
//
// By default, this method looks for a `-decode<Key>WithCoder:modelVersion:`
// method on the receiver, and invokes it if found.
//
// If the custom method is not implemented and `coder` does not require secure
// coding, `-[NSCoder decodeObjectForKey:]` will be invoked with the given
// `key`.
//
// If the custom method is not implemented and `coder` requires secure coding,
// `-[NSCoder decodeObjectOfClasses:forKey:]` will be invoked with the
// information from +allowedSecureCodingClassesByPropertyKey and the given `key`. The
// receiver must conform to <NSSecureCoding> for this to work correctly.
//
// key - The property key to decode the value for. This argument cannot
// be nil.
// coder - The NSCoder representing the archive being decoded. This
// argument cannot be nil.
// modelVersion - The version of the original model object that was encoded.
//
// Returns the decoded and boxed value, or nil if the key was not present.
- (id)decodeValueForKey:(NSString *)key withCoder:(NSCoder *)coder modelVersion:(NSUInteger)modelVersion;
// The version of this MTLModel subclass.
//
// This version number is saved in archives so that later model changes can be
// made backwards-compatible with old versions.
//
// Subclasses should override this method to return a higher version number
// whenever a breaking change is made to the model.
//
// Returns 0.
+ (NSUInteger)modelVersion;
@end
// This method must be overridden to support archives created by older versions
// of Mantle (before the `MTLModel+NSCoding` interface existed).
@interface MTLModel (OldArchiveSupport)
// Converts an archived external representation to a dictionary suitable for
// passing to -initWithDictionary:.
//
// externalRepresentation - The decoded external representation of the receiver.
// fromVersion - The model version at the time the external
// representation was encoded.
//
// Returns nil by default, indicating that conversion failed.
+ (NSDictionary *)dictionaryValueFromArchivedExternalRepresentation:(NSDictionary *)externalRepresentation version:(NSUInteger)fromVersion;
@end
@@ -0,0 +1,125 @@
//
// MTLModel.h
// Mantle
//
// Created by Justin Spahr-Summers on 2012-09-11.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import <Foundation/Foundation.h>
// An abstract base class for model objects, using reflection to provide
// sensible default behaviors.
//
// The default implementations of <NSCopying>, -hash, and -isEqual: make use of
// the +propertyKeys method.
@interface MTLModel : NSObject <NSCopying>
// Returns a new instance of the receiver initialized using
// -initWithDictionary:error:.
+ (instancetype)modelWithDictionary:(NSDictionary *)dictionaryValue error:(NSError **)error;
// Initializes the receiver with default values.
//
// This is the designated initializer for this class.
- (instancetype)init;
// Initializes the receiver using key-value coding, setting the keys and values
// in the given dictionary.
//
// Subclass implementations may override this method, calling the super
// implementation, in order to perform further processing and initialization
// after deserialization.
//
// dictionaryValue - Property keys and values to set on the receiver. Any NSNull
// values will be converted to nil before being used. KVC
// validation methods will automatically be invoked for all of
// the properties given. If nil, this method is equivalent to
// -init.
// error - If not NULL, this may be set to any error that occurs
// (like a KVC validation error).
//
// Returns an initialized model object, or nil if validation failed.
- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError **)error;
// Returns the keys for all @property declarations, except for `readonly`
// properties without ivars, or properties on MTLModel itself.
+ (NSSet *)propertyKeys;
// A dictionary representing the properties of the receiver.
//
// The default implementation combines the values corresponding to all
// +propertyKeys into a dictionary, with any nil values represented by NSNull.
//
// This property must never be nil.
@property (nonatomic, copy, readonly) NSDictionary *dictionaryValue;
// Merges the value of the given key on the receiver with the value of the same
// key from the given model object, giving precedence to the other model object.
//
// By default, this method looks for a `-merge<Key>FromModel:` method on the
// receiver, and invokes it if found. If not found, and `model` is not nil, the
// value for the given key is taken from `model`.
- (void)mergeValueForKey:(NSString *)key fromModel:(MTLModel *)model;
// Merges the values of the given model object into the receiver, using
// -mergeValueForKey:fromModel: for each key in +propertyKeys.
//
// `model` must be an instance of the receiver's class or a subclass thereof.
- (void)mergeValuesForKeysFromModel:(MTLModel *)model;
// Compares the receiver with another object for equality.
//
// The default implementation is equivalent to comparing both models'
// -dictionaryValue.
//
// Note that this may lead to infinite loops if the receiver holds a circular
// reference to another MTLModel and both use the default behavior.
// It is recommended to override -isEqual: in this scenario.
- (BOOL)isEqual:(id)object;
// A string that describes the contents of the receiver.
//
// The default implementation is based on the receiver's class and its
// -dictionaryValue.
//
// Note that this may lead to infinite loops if the receiver holds a circular
// reference to another MTLModel and both use the default behavior.
// It is recommended to override -description in this scenario.
- (NSString *)description;
@end
// Implements validation logic for MTLModel.
@interface MTLModel (Validation)
// Validates the model.
//
// The default implementation simply invokes -validateValue:forKey:error: with
// all +propertyKeys and their current value. If -validateValue:forKey:error:
// returns a new value, the property is set to that new value.
//
// error - If not NULL, this may be set to any error that occurs during
// validation
//
// Returns YES if the model is valid, or NO if the validation failed.
- (BOOL)validate:(NSError **)error;
@end
@interface MTLModel (Unavailable)
+ (instancetype)modelWithDictionary:(NSDictionary *)dictionaryValue __attribute__((deprecated("Replaced by +modelWithDictionary:error:")));
- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue __attribute__((deprecated("Replaced by -initWithDictionary:error:")));
+ (instancetype)modelWithExternalRepresentation:(NSDictionary *)externalRepresentation __attribute__((deprecated("Replaced by -[MTLJSONAdapter initWithJSONDictionary:modelClass:]")));
- (instancetype)initWithExternalRepresentation:(NSDictionary *)externalRepresentation __attribute__((deprecated("Replaced by -[MTLJSONAdapter initWithJSONDictionary:modelClass:]")));
@property (nonatomic, copy, readonly) NSDictionary *externalRepresentation __attribute__((deprecated("Replaced by MTLJSONAdapter.JSONDictionary")));
+ (NSDictionary *)externalRepresentationKeyPathsByPropertyKey __attribute__((deprecated("Replaced by +JSONKeyPathsByPropertyKey in <MTLJSONSerializing>")));
+ (NSValueTransformer *)transformerForKey:(NSString *)key __attribute__((deprecated("Replaced by +JSONTransformerForKey: in <MTLJSONSerializing>")));
+ (NSDictionary *)migrateExternalRepresentation:(NSDictionary *)externalRepresentation fromVersion:(NSUInteger)fromVersion __attribute__((deprecated("Replaced by -decodeValueForKey:withCoder:modelVersion:")));
@end
@@ -0,0 +1,29 @@
//
// MTLValueTransformer.h
// Mantle
//
// Created by Justin Spahr-Summers on 2012-09-11.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef id (^MTLValueTransformerBlock)(id);
//
// A value transformer supporting block-based transformation.
//
@interface MTLValueTransformer : NSValueTransformer
// Returns a transformer which transforms values using the given block. Reverse
// transformations will not be allowed.
+ (instancetype)transformerWithBlock:(MTLValueTransformerBlock)transformationBlock;
// Returns a transformer which transforms values using the given block, for
// forward or reverse transformations.
+ (instancetype)reversibleTransformerWithBlock:(MTLValueTransformerBlock)transformationBlock;
// Returns a transformer which transforms values using the given blocks.
+ (instancetype)reversibleTransformerWithForwardBlock:(MTLValueTransformerBlock)forwardBlock reverseBlock:(MTLValueTransformerBlock)reverseBlock;
@end
@@ -0,0 +1,26 @@
//
// Mantle.h
// Mantle
//
// Created by Justin Spahr-Summers on 2012-09-04.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import <Foundation/Foundation.h>
//! Project version number for Mantle.
FOUNDATION_EXPORT double MantleVersionNumber;
//! Project version string for Mantle.
FOUNDATION_EXPORT const unsigned char MantleVersionString[];
#import <Mantle/MTLJSONAdapter.h>
#import <Mantle/MTLManagedObjectAdapter.h>
#import <Mantle/MTLModel.h>
#import <Mantle/MTLModel+NSCoding.h>
#import <Mantle/MTLValueTransformer.h>
#import <Mantle/NSArray+MTLManipulationAdditions.h>
#import <Mantle/NSDictionary+MTLManipulationAdditions.h>
#import <Mantle/NSObject+MTLComparisonAdditions.h>
#import <Mantle/NSValueTransformer+MTLInversionAdditions.h>
#import <Mantle/NSValueTransformer+MTLPredefinedTransformerAdditions.h>
@@ -0,0 +1,28 @@
//
// NSArray+MTLManipulationAdditions.h
// Mantle
//
// Created by Josh Abernathy on 9/19/12.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSArray (MTLManipulationAdditions)
// The first object in the array or nil if the array is empty.
// Forwards to `firstObject` which has been first declared in iOS7, but works with iOS4/10.6.
@property (nonatomic, readonly, strong) id mtl_firstObject;
// Returns a new array without all instances of the given object.
- (NSArray *)mtl_arrayByRemovingObject:(id)object;
// Returns a new array without the first object. If the array is empty, it
// returns the empty array.
- (NSArray *)mtl_arrayByRemovingFirstObject;
// Returns a new array without the last object. If the array is empty, it
// returns the empty array.
- (NSArray *)mtl_arrayByRemovingLastObject;
@end
@@ -0,0 +1,25 @@
//
// NSDictionary+MTLManipulationAdditions.h
// Mantle
//
// Created by Justin Spahr-Summers on 2012-09-24.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSDictionary (MTLManipulationAdditions)
// Merges the keys and values from the given dictionary into the receiver. If
// both the receiver and `dictionary` have a given key, the value from
// `dictionary` is used.
//
// Returns a new dictionary containing the entries of the receiver combined with
// those of `dictionary`.
- (NSDictionary *)mtl_dictionaryByAddingEntriesFromDictionary:(NSDictionary *)dictionary;
// Creates a new dictionary with all the entries for the given keys removed from
// the receiver.
- (NSDictionary *)mtl_dictionaryByRemovingEntriesWithKeys:(NSSet *)keys;
@end
@@ -0,0 +1,15 @@
//
// NSObject+MTLComparisonAdditions.h
// Mantle
//
// Created by Josh Vera on 10/26/12.
// Copyright (c) 2012 GitHub. All rights reserved.
//
// Portions copyright (c) 2011 Bitswift. All rights reserved.
// See the LICENSE file for more information.
//
#import <Foundation/Foundation.h>
// Returns whether both objects are identical or equal via -isEqual:
BOOL MTLEqualObjects(id obj1, id obj2);
@@ -0,0 +1,21 @@
//
// NSValueTransformer+MTLInversionAdditions.h
// Mantle
//
// Created by Justin Spahr-Summers on 2013-05-18.
// Copyright (c) 2013 GitHub. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSValueTransformer (MTLInversionAdditions)
// Flips the direction of the receiver's transformation, such that
// -transformedValue: will become -reverseTransformedValue:, and vice-versa.
//
// The receiver must allow reverse transformation.
//
// Returns an inverted transformer.
- (NSValueTransformer *)mtl_invertedTransformer;
@end
@@ -0,0 +1,84 @@
//
// NSValueTransformer+MTLPredefinedTransformerAdditions.h
// Mantle
//
// Created by Justin Spahr-Summers on 2012-09-27.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import <Foundation/Foundation.h>
// The name for a value transformer that converts strings into URLs and back.
extern NSString * const MTLURLValueTransformerName;
// Ensure an NSNumber is backed by __NSCFBoolean/CFBooleanRef
//
// NSJSONSerialization, and likely other serialization libraries, ordinarily
// serialize NSNumbers as numbers, and thus booleans would be serialized as
// 0/1. The exception is when the NSNumber is backed by __NSCFBoolean, which,
// though very much an implementation detail, is detected and serialized as a
// proper boolean.
extern NSString * const MTLBooleanValueTransformerName;
@interface NSValueTransformer (MTLPredefinedTransformerAdditions)
// Creates a reversible transformer to convert a JSON dictionary into a MTLModel
// object, and vice-versa.
//
// modelClass - The MTLModel subclass to attempt to parse from the JSON. This
// class must conform to <MTLJSONSerializing>. This argument must
// not be nil.
//
// Returns a reversible transformer which uses MTLJSONAdapter for transforming
// values back and forth.
+ (NSValueTransformer *)mtl_JSONDictionaryTransformerWithModelClass:(Class)modelClass;
// Creates a reversible transformer to convert an array of JSON dictionaries
// into an array of MTLModel objects, and vice-versa.
//
// modelClass - The MTLModel subclass to attempt to parse from each JSON
// dictionary. This class must conform to <MTLJSONSerializing>.
// This argument must not be nil.
//
// Returns a reversible transformer which uses MTLJSONAdapter for transforming
// array elements back and forth.
+ (NSValueTransformer *)mtl_JSONArrayTransformerWithModelClass:(Class)modelClass;
// A reversible value transformer to transform between the keys and objects of a
// dictionary.
//
// dictionary - The dictionary whose keys and values should be
// transformed between. This argument must not be nil.
// defaultValue - The result to fall back to, in case no key matching the
// input value was found during a forward transformation.
// reverseDefaultValue - The result to fall back to, in case no value matching
// the input value was found during a reverse
// transformation.
//
// Can for example be used for transforming between enum values and their string
// representation.
//
// NSValueTransformer *valueTransformer = [NSValueTransformer mtl_valueMappingTransformerWithDictionary:@{
// @"foo": @(EnumDataTypeFoo),
// @"bar": @(EnumDataTypeBar),
// } defaultValue: @(EnumDataTypeUndefined) reverseDefaultValue: @"undefined"];
//
// Returns a transformer that will map from keys to values in dictionary
// for forward transformation, and from values to keys for reverse
// transformations. If no matching key or value can be found, the respective
// default value is returned.
+ (NSValueTransformer *)mtl_valueMappingTransformerWithDictionary:(NSDictionary *)dictionary defaultValue:(id)defaultValue reverseDefaultValue:(id)reverseDefaultValue;
// Returns a value transformer created by calling
// `+mtl_valueMappingTransformerWithDictionary:defaultValue:reverseDefaultValue:`
// with a default value of `nil` and a reverse default value of `nil`.
+ (NSValueTransformer *)mtl_valueMappingTransformerWithDictionary:(NSDictionary *)dictionary;
@end
@interface NSValueTransformer (UnavailableMTLPredefinedTransformerAdditions)
+ (NSValueTransformer *)mtl_externalRepresentationTransformerWithModelClass:(Class)modelClass __attribute__((deprecated("Replaced by +mtl_JSONDictionaryTransformerWithModelClass:")));
+ (NSValueTransformer *)mtl_externalRepresentationArrayTransformerWithModelClass:(Class)modelClass __attribute__((deprecated("Replaced by +mtl_JSONArrayTransformerWithModelClass:")));
@end
@@ -0,0 +1,6 @@
framework module Mantle {
umbrella header "Mantle.h"
export *
module * { export * }
}
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>16F2104</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>Mantle</string>
<key>CFBundleIdentifier</key>
<string>org.mantle.Mantle</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Mantle</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string></string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>8E3004b</string>
<key>DTPlatformVersion</key>
<string>GM</string>
<key>DTSDKBuild</key>
<string>16E185</string>
<key>DTSDKName</key>
<string>macosx10.12</string>
<key>DTXcode</key>
<string>0833</string>
<key>DTXcodeBuild</key>
<string>8E3004b</string>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
</dict>
</plist>
@@ -0,0 +1,264 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict>
<key>Resources/Info.plist</key>
<data>
5gOxsGY3VGVvebnR8eP1z3BDrls=
</data>
</dict>
<key>files2</key>
<dict>
<key>Headers/MTLJSONAdapter.h</key>
<dict>
<key>hash</key>
<data>
HIypP9pGI7Wk97RKy3thGsC7LSg=
</data>
<key>hash2</key>
<data>
YhksDrIzFk7TQllZD4eF2qEubtCfFlDrddfBeJEvpoc=
</data>
</dict>
<key>Headers/MTLManagedObjectAdapter.h</key>
<dict>
<key>hash</key>
<data>
tjTyEh7TlZBAXcyaY1I1SmXHbE4=
</data>
<key>hash2</key>
<data>
WxYp7uqr+H3R5UnwMpDSHlxSt7MaAPhQob/BDXlWbVI=
</data>
</dict>
<key>Headers/MTLModel+NSCoding.h</key>
<dict>
<key>hash</key>
<data>
2tdybjH/C8ZU8VcZrX4oliXkuM8=
</data>
<key>hash2</key>
<data>
QuzPKm76puk2/uZN0s9Jqn7g5AFF6xFbnBsN450IXfc=
</data>
</dict>
<key>Headers/MTLModel.h</key>
<dict>
<key>hash</key>
<data>
9c2w4hZo7o/vLu/3TaZ2vXXjwxc=
</data>
<key>hash2</key>
<data>
2JEc6fCePU0odH+dDY8947O0/wS1Ey96Vuun1HCP564=
</data>
</dict>
<key>Headers/MTLValueTransformer.h</key>
<dict>
<key>hash</key>
<data>
m6veNGSL5/Kif8yDXH5zU+2CaK8=
</data>
<key>hash2</key>
<data>
AnO4t0Ur5lBz44l14XRXgB3g6JEiUE1JBfPdvV+H7fA=
</data>
</dict>
<key>Headers/Mantle.h</key>
<dict>
<key>hash</key>
<data>
TYIHtg+qqzl8yG0InqTB7Oyr63A=
</data>
<key>hash2</key>
<data>
YKN3UxwC4VdZ19A/VN8Ndpdf5PVHbZlYMb9K1etJKKg=
</data>
</dict>
<key>Headers/NSArray+MTLManipulationAdditions.h</key>
<dict>
<key>hash</key>
<data>
JkGLHSSIKlmCin6flems7m4llvE=
</data>
<key>hash2</key>
<data>
fQTG4WuuoxdNhgn6wDU29iy18nNqFwlK9F45NRbpD6w=
</data>
</dict>
<key>Headers/NSDictionary+MTLManipulationAdditions.h</key>
<dict>
<key>hash</key>
<data>
bqGP1ngOZeJ16AY5Vi/2ObZCmlw=
</data>
<key>hash2</key>
<data>
0Jf2Da8WIPOoV0BkwF1D69nsUwbgypRDMVoedgtuJ24=
</data>
</dict>
<key>Headers/NSObject+MTLComparisonAdditions.h</key>
<dict>
<key>hash</key>
<data>
2oBONz9+tBIWVjYGxYBIBT0b0n8=
</data>
<key>hash2</key>
<data>
86zL7jX96Spa7xT5HQJ+aYmtXD1oKLUrU4JtlO2cIqU=
</data>
</dict>
<key>Headers/NSValueTransformer+MTLInversionAdditions.h</key>
<dict>
<key>hash</key>
<data>
sxXToKdUH8yMt45aTwPeM3hKmjs=
</data>
<key>hash2</key>
<data>
6xpkrCw6Z2B3EOFKGMBi6o78Hau0v0zsfGJeQMAk354=
</data>
</dict>
<key>Headers/NSValueTransformer+MTLPredefinedTransformerAdditions.h</key>
<dict>
<key>hash</key>
<data>
HhBKFxBsCIBfvD6Rm6d3yjVk540=
</data>
<key>hash2</key>
<data>
QAg6gyiNeq39mqhxZ/OttPualO+F1x2E99sNuyNE1ZE=
</data>
</dict>
<key>Modules/module.modulemap</key>
<dict>
<key>hash</key>
<data>
Bey/HDj6mfWAMaHrd42tLo2G6HA=
</data>
<key>hash2</key>
<data>
kjfaRu6wmQ32w9Db79wuDbkttAjOMC2jrLbtCbIzrSM=
</data>
</dict>
<key>Resources/Info.plist</key>
<dict>
<key>hash</key>
<data>
5gOxsGY3VGVvebnR8eP1z3BDrls=
</data>
<key>hash2</key>
<data>
e6zwAZD1Y0oZhr5vq5p/9RM9J6llqdDsogAev9b3loI=
</data>
</dict>
</dict>
<key>rules</key>
<dict>
<key>^Resources/</key>
<true/>
<key>^Resources/.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^Resources/.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Resources/Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
<dict>
<key>nested</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>^.*</key>
<true/>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^Resources/</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^Resources/.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^Resources/.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Resources/Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^[^/]+$</key>
<dict>
<key>nested</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>
@@ -0,0 +1 @@
Versions/Current/Headers
@@ -0,0 +1 @@
Versions/Current/Modules
@@ -0,0 +1 @@
Versions/Current/ReactiveCocoa
@@ -0,0 +1 @@
Versions/Current/Resources
@@ -0,0 +1,68 @@
//
// EXTKeyPathCoding.h
// extobjc
//
// Created by Justin Spahr-Summers on 19.06.12.
// Copyright (C) 2012 Justin Spahr-Summers.
// Released under the MIT license.
//
#import <Foundation/Foundation.h>
#import "metamacros.h"
/**
* \@keypath allows compile-time verification of key paths. Given a real object
* receiver and key path:
*
* @code
NSString *UTF8StringPath = @keypath(str.lowercaseString.UTF8String);
// => @"lowercaseString.UTF8String"
NSString *versionPath = @keypath(NSObject, version);
// => @"version"
NSString *lowercaseStringPath = @keypath(NSString.new, lowercaseString);
// => @"lowercaseString"
* @endcode
*
* ... the macro returns an \c NSString containing all but the first path
* component or argument (e.g., @"lowercaseString.UTF8String", @"version").
*
* In addition to simply creating a key path, this macro ensures that the key
* path is valid at compile-time (causing a syntax error if not), and supports
* refactoring, such that changing the name of the property will also update any
* uses of \@keypath.
*/
#define keypath(...) \
metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__))(keypath1(__VA_ARGS__))(keypath2(__VA_ARGS__))
#define keypath1(PATH) \
(((void)(NO && ((void)PATH, NO)), strchr(# PATH, '.') + 1))
#define keypath2(OBJ, PATH) \
(((void)(NO && ((void)OBJ.PATH, NO)), # PATH))
/**
* \@collectionKeypath allows compile-time verification of key paths across collections NSArray/NSSet etc. Given a real object
* receiver, collection object receiver and related keypaths:
*
* @code
NSString *employessFirstNamePath = @collectionKeypath(department.employees, Employee.new, firstName)
// => @"employees.firstName"
NSString *employessFirstNamePath = @collectionKeypath(Department.new, employees, Employee.new, firstName)
// => @"employees.firstName"
* @endcode
*
*/
#define collectionKeypath(...) \
metamacro_if_eq(3, metamacro_argcount(__VA_ARGS__))(collectionKeypath3(__VA_ARGS__))(collectionKeypath4(__VA_ARGS__))
#define collectionKeypath3(PATH, COLLECTION_OBJECT, COLLECTION_PATH) ([[NSString stringWithFormat:@"%s.%s",keypath(PATH), keypath(COLLECTION_OBJECT, COLLECTION_PATH)] UTF8String])
#define collectionKeypath4(OBJ, PATH, COLLECTION_OBJECT, COLLECTION_PATH) ([[NSString stringWithFormat:@"%s.%s",keypath(OBJ, PATH), keypath(COLLECTION_OBJECT, COLLECTION_PATH)] UTF8String])
@@ -0,0 +1,118 @@
//
// EXTScope.h
// extobjc
//
// Created by Justin Spahr-Summers on 2011-05-04.
// Copyright (C) 2012 Justin Spahr-Summers.
// Released under the MIT license.
//
#import "metamacros.h"
/**
* \@onExit defines some code to be executed when the current scope exits. The
* code must be enclosed in braces and terminated with a semicolon, and will be
* executed regardless of how the scope is exited, including from exceptions,
* \c goto, \c return, \c break, and \c continue.
*
* Provided code will go into a block to be executed later. Keep this in mind as
* it pertains to memory management, restrictions on assignment, etc. Because
* the code is used within a block, \c return is a legal (though perhaps
* confusing) way to exit the cleanup block early.
*
* Multiple \@onExit statements in the same scope are executed in reverse
* lexical order. This helps when pairing resource acquisition with \@onExit
* statements, as it guarantees teardown in the opposite order of acquisition.
*
* @note This statement cannot be used within scopes defined without braces
* (like a one line \c if). In practice, this is not an issue, since \@onExit is
* a useless construct in such a case anyways.
*/
#define onExit \
rac_keywordify \
__strong rac_cleanupBlock_t metamacro_concat(rac_exitBlock_, __LINE__) __attribute__((cleanup(rac_executeCleanupBlock), unused)) = ^
/**
* Creates \c __weak shadow variables for each of the variables provided as
* arguments, which can later be made strong again with #strongify.
*
* This is typically used to weakly reference variables in a block, but then
* ensure that the variables stay alive during the actual execution of the block
* (if they were live upon entry).
*
* See #strongify for an example of usage.
*/
#define weakify(...) \
rac_keywordify \
metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)
/**
* Like #weakify, but uses \c __unsafe_unretained instead, for targets or
* classes that do not support weak references.
*/
#define unsafeify(...) \
rac_keywordify \
metamacro_foreach_cxt(rac_weakify_,, __unsafe_unretained, __VA_ARGS__)
/**
* Strongly references each of the variables provided as arguments, which must
* have previously been passed to #weakify.
*
* The strong references created will shadow the original variable names, such
* that the original names can be used without issue (and a significantly
* reduced risk of retain cycles) in the current scope.
*
* @code
id foo = [[NSObject alloc] init];
id bar = [[NSObject alloc] init];
@weakify(foo, bar);
// this block will not keep 'foo' or 'bar' alive
BOOL (^matchesFooOrBar)(id) = ^ BOOL (id obj){
// but now, upon entry, 'foo' and 'bar' will stay alive until the block has
// finished executing
@strongify(foo, bar);
return [foo isEqual:obj] || [bar isEqual:obj];
};
* @endcode
*/
#define strongify(...) \
rac_keywordify \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
metamacro_foreach(rac_strongify_,, __VA_ARGS__) \
_Pragma("clang diagnostic pop")
/*** implementation details follow ***/
typedef void (^rac_cleanupBlock_t)();
static inline void rac_executeCleanupBlock (__strong rac_cleanupBlock_t *block) {
(*block)();
}
#define rac_weakify_(INDEX, CONTEXT, VAR) \
CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);
#define rac_strongify_(INDEX, VAR) \
__strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);
// Details about the choice of backing keyword:
//
// The use of @try/@catch/@finally can cause the compiler to suppress
// return-type warnings.
// The use of @autoreleasepool {} is not optimized away by the compiler,
// resulting in superfluous creation of autorelease pools.
//
// Since neither option is perfect, and with no other alternatives, the
// compromise is to use @autorelease in DEBUG builds to maintain compiler
// analysis, and to use @try/@catch otherwise to avoid insertion of unnecessary
// autorelease pools.
#if DEBUG
#define rac_keywordify autoreleasepool {}
#else
#define rac_keywordify try {} @catch (...) {}
#endif
@@ -0,0 +1,20 @@
//
// NSArray+RACSequenceAdditions.h
// ReactiveCocoa
//
// Created by Justin Spahr-Summers on 2012-10-29.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSequence;
@interface NSArray (RACSequenceAdditions)
/// Creates and returns a sequence corresponding to the receiver.
///
/// Mutating the receiver will not affect the sequence after it's been created.
@property (nonatomic, copy, readonly) RACSequence *rac_sequence;
@end
@@ -0,0 +1,22 @@
//
// NSControl+RACCommandSupport.h
// ReactiveCocoa
//
// Created by Josh Abernathy on 3/3/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@class RACCommand;
@interface NSControl (RACCommandSupport)
/// Sets the control's command. When the control is clicked, the command is
/// executed with the sender of the event. The control's enabledness is bound
/// to the command's `canExecute`.
///
/// Note: this will reset the control's target and action.
@property (nonatomic, strong) RACCommand *rac_command;
@end
@@ -0,0 +1,24 @@
//
// NSControl+RACTextSignalSupport.h
// ReactiveCocoa
//
// Created by Justin Spahr-Summers on 2013-03-08.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@class RACSignal;
@interface NSControl (RACTextSignalSupport)
/// Observes a text-based control for changes.
///
/// Using this method on a control without editable text is considered undefined
/// behavior.
///
/// Returns a signal which sends the current string value of the receiver, then
/// the new value any time it changes.
- (RACSignal *)rac_textSignal;
@end
@@ -0,0 +1,22 @@
//
// NSData+RACSupport.h
// ReactiveCocoa
//
// Created by Josh Abernathy on 5/11/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACScheduler;
@class RACSignal;
@interface NSData (RACSupport)
// Read the data at the URL using -[NSData initWithContentsOfURL:options:error:].
// Sends the data or the error.
//
// scheduler - cannot be nil.
+ (RACSignal *)rac_readContentsOfURL:(NSURL *)URL options:(NSDataReadingOptions)options scheduler:(RACScheduler *)scheduler;
@end
@@ -0,0 +1,31 @@
//
// NSDictionary+RACSequenceAdditions.h
// ReactiveCocoa
//
// Created by Justin Spahr-Summers on 2012-10-29.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSequence;
@interface NSDictionary (RACSequenceAdditions)
/// Creates and returns a sequence of RACTuple key/value pairs. The key will be
/// the first element in the tuple, and the value will be the second.
///
/// Mutating the receiver will not affect the sequence after it's been created.
@property (nonatomic, copy, readonly) RACSequence *rac_sequence;
/// Creates and returns a sequence corresponding to the keys in the receiver.
///
/// Mutating the receiver will not affect the sequence after it's been created.
@property (nonatomic, copy, readonly) RACSequence *rac_keySequence;
/// Creates and returns a sequence corresponding to the values in the receiver.
///
/// Mutating the receiver will not affect the sequence after it's been created.
@property (nonatomic, copy, readonly) RACSequence *rac_valueSequence;
@end
@@ -0,0 +1,20 @@
//
// NSEnumerator+RACSequenceAdditions.h
// ReactiveCocoa
//
// Created by Uri Baghin on 07/01/2013.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSequence;
@interface NSEnumerator (RACSequenceAdditions)
/// Creates and returns a sequence corresponding to the receiver.
///
/// The receiver is exhausted lazily as the sequence is enumerated.
@property (nonatomic, copy, readonly) RACSequence *rac_sequence;
@end
@@ -0,0 +1,19 @@
//
// NSFileHandle+RACSupport.h
// ReactiveCocoa
//
// Created by Josh Abernathy on 5/10/12.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSignal;
@interface NSFileHandle (RACSupport)
// Read any available data in the background and send it. Completes when data
// length is <= 0.
- (RACSignal *)rac_readInBackground;
@end
@@ -0,0 +1,21 @@
//
// NSIndexSet+RACSequenceAdditions.h
// ReactiveCocoa
//
// Created by Sergey Gavrilyuk on 12/17/13.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSequence;
@interface NSIndexSet (RACSequenceAdditions)
/// Creates and returns a sequence of indexes (as `NSNumber`s) corresponding to
/// the receiver.
///
/// Mutating the receiver will not affect the sequence after it's been created.
@property (nonatomic, copy, readonly) RACSequence *rac_sequence;
@end
@@ -0,0 +1,18 @@
//
// NSNotificationCenter+RACSupport.h
// ReactiveCocoa
//
// Created by Josh Abernathy on 5/10/12.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSignal;
@interface NSNotificationCenter (RACSupport)
// Sends the NSNotification every time the notification is posted.
- (RACSignal *)rac_addObserverForName:(NSString *)notificationName object:(id)object;
@end
@@ -0,0 +1,40 @@
//
// NSObject+RACAppKitBindings.h
// ReactiveCocoa
//
// Created by Josh Abernathy on 4/17/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@class RACChannelTerminal;
@interface NSObject (RACAppKitBindings)
/// Invokes -rac_channelToBinding:options: without any options.
- (RACChannelTerminal *)rac_channelToBinding:(NSString *)binding;
/// Applies a Cocoa binding to the receiver, then exposes a RACChannel-based
/// interface for manipulating it.
///
/// Creating two of the same bindings on the same object will result in undefined
/// behavior.
///
/// binding - The name of the binding. This must not be nil.
/// options - Any options to pass to Cocoa Bindings. This may be nil.
///
/// Returns a RACChannelTerminal which will send future values from the receiver,
/// and update the receiver when values are sent to the terminal.
- (RACChannelTerminal *)rac_channelToBinding:(NSString *)binding options:(NSDictionary *)options;
@end
@interface NSObject (RACAppKitBindingsDeprecated)
- (void)rac_bind:(NSString *)binding toObject:(id)object withKeyPath:(NSString *)keyPath __attribute__((deprecated("Use -rac_bind:options: instead")));
- (void)rac_bind:(NSString *)binding toObject:(id)object withKeyPath:(NSString *)keyPath nilValue:(id)nilValue __attribute__((deprecated("Use -rac_bind:options: instead")));
- (void)rac_bind:(NSString *)binding toObject:(id)object withKeyPath:(NSString *)keyPath transform:(id (^)(id value))transformBlock __attribute__((deprecated("Use -rac_bind:options: instead")));
- (void)rac_bind:(NSString *)binding toObject:(id)object withNegatedKeyPath:(NSString *)keyPath __attribute__((deprecated("Use -rac_bind:options: instead")));
@end
@@ -0,0 +1,34 @@
//
// NSObject+RACDeallocating.h
// ReactiveCocoa
//
// Created by Kazuo Koga on 2013/03/15.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACCompoundDisposable;
@class RACDisposable;
@class RACSignal;
@interface NSObject (RACDeallocating)
/// The compound disposable which will be disposed of when the receiver is
/// deallocated.
@property (atomic, readonly, strong) RACCompoundDisposable *rac_deallocDisposable;
/// Returns a signal that will complete immediately before the receiver is fully
/// deallocated. If already deallocated when the signal is subscribed to,
/// a `completed` event will be sent immediately.
- (RACSignal *)rac_willDeallocSignal;
@end
@interface NSObject (RACDeallocatingDeprecated)
- (RACSignal *)rac_didDeallocSignal __attribute__((deprecated("Use -rac_willDeallocSignal")));
- (void)rac_addDeallocDisposable:(RACDisposable *)disposable __attribute__((deprecated("Add disposables to -rac_deallocDisposable instead")));
@end
@@ -0,0 +1,61 @@
//
// NSObject+RACLifting.h
// ReactiveCocoa
//
// Created by Josh Abernathy on 10/13/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSignal;
@interface NSObject (RACLifting)
/// Lifts the selector on the receiver into the reactive world. The selector will
/// be invoked whenever any signal argument sends a value, but only after each
/// signal has sent an initial value.
///
/// It will replay the most recently sent value to new subscribers.
///
/// This does not support C arrays or unions.
///
/// selector - The selector on self to invoke.
/// firstSignal - The signal corresponding to the first method argument. This
/// must not be nil.
/// ... - A list of RACSignals corresponding to the remaining arguments.
/// There must be a non-nil signal for each method argument.
///
/// Examples
///
/// [button rac_liftSelector:@selector(setTitleColor:forState:) withSignals:textColorSignal, [RACSignal return:@(UIControlStateNormal)], nil];
///
/// Returns a signal which sends the return value from each invocation of the
/// selector. If the selector returns void, it instead sends RACUnit.defaultUnit.
/// It completes only after all the signal arguments complete.
- (RACSignal *)rac_liftSelector:(SEL)selector withSignals:(RACSignal *)firstSignal, ... NS_REQUIRES_NIL_TERMINATION;
/// Like -rac_liftSelector:withSignals:, but accepts an array instead of
/// a variadic list of arguments.
- (RACSignal *)rac_liftSelector:(SEL)selector withSignalsFromArray:(NSArray *)signals;
/// Like -rac_liftSelector:withSignals:, but accepts a signal sending tuples of
/// arguments instead of a variadic list of arguments.
- (RACSignal *)rac_liftSelector:(SEL)selector withSignalOfArguments:(RACSignal *)arguments;
@end
@interface NSObject (RACLiftingDeprecated)
- (RACSignal *)rac_liftSelector:(SEL)selector withObjects:(id)arg, ... __attribute__((deprecated("Use -rac_liftSelector:withSignals: instead")));
- (RACSignal *)rac_liftSelector:(SEL)selector withObjectsFromArray:(NSArray *)args __attribute__((deprecated("Use -rac_liftSelector:withSignalsFromArray: instead")));
- (RACSignal *)rac_liftBlock:(id)block withArguments:(id)arg, ... NS_REQUIRES_NIL_TERMINATION __attribute__((deprecated("Use +combineLatest:reduce: instead")));
- (RACSignal *)rac_liftBlock:(id)block withArgumentsFromArray:(NSArray *)args __attribute__((deprecated("Use +combineLatest:reduce: instead")));
@end
@interface NSObject (RACLiftingUnavailable)
- (instancetype)rac_lift __attribute__((unavailable("Use -rac_liftSelector:withSignals: instead")));
@end
@@ -0,0 +1,108 @@
//
// NSObject+RACPropertySubscribing.h
// ReactiveCocoa
//
// Created by Josh Abernathy on 3/2/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "EXTKeyPathCoding.h"
#import "metamacros.h"
/// Creates a signal which observes `KEYPATH` on `TARGET` for changes.
///
/// In either case, the observation continues until `TARGET` _or self_ is
/// deallocated. If any intermediate object is deallocated instead, it will be
/// assumed to have been set to nil.
///
/// Make sure to `@strongify(self)` when using this macro within a block! The
/// macro will _always_ reference `self`, which can silently introduce a retain
/// cycle within a block. As a result, you should make sure that `self` is a weak
/// reference (e.g., created by `@weakify` and `@strongify`) before the
/// expression that uses `RACObserve`.
///
/// Examples
///
/// // Observes self, and doesn't stop until self is deallocated.
/// RACSignal *selfSignal = RACObserve(self, arrayController.items);
///
/// // Observes the array controller, and stops when self _or_ the array
/// // controller is deallocated.
/// RACSignal *arrayControllerSignal = RACObserve(self.arrayController, items);
///
/// // Observes obj.arrayController, and stops when self _or_ the array
/// // controller is deallocated.
/// RACSignal *signal2 = RACObserve(obj.arrayController, items);
///
/// @weakify(self);
/// RACSignal *signal3 = [anotherSignal flattenMap:^(NSArrayController *arrayController) {
/// // Avoids a retain cycle because of RACObserve implicitly referencing
/// // self.
/// @strongify(self);
/// return RACObserve(arrayController, items);
/// }];
///
/// Returns a signal which sends the current value of the key path on
/// subscription, then sends the new value every time it changes, and sends
/// completed if self or observer is deallocated.
#define RACObserve(TARGET, KEYPATH) \
({ \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wreceiver-is-weak\"") \
__weak id target_ = (TARGET); \
[target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \
_Pragma("clang diagnostic pop") \
})
@class RACDisposable;
@class RACSignal;
@interface NSObject (RACPropertySubscribing)
/// Creates a signal to observe the value at the given key path.
///
/// The initial value is sent on subscription, the subsequent values are sent
/// from whichever thread the change occured on, even if it doesn't have a valid
/// scheduler.
///
/// Returns a signal that immediately sends the receiver's current value at the
/// given keypath, then any changes thereafter.
- (RACSignal *)rac_valuesForKeyPath:(NSString *)keyPath observer:(__weak NSObject *)observer;
/// Creates a signal to observe the changes of the given key path.
///
/// The initial value is sent on subscription, the subsequent values are sent
/// from whichever thread the change occured on, even if it doesn't have a valid
/// scheduler.
///
/// Returns a signal that sends tuples containing the current value at the key
/// path and the change dictionary for each KVO callback.
- (RACSignal *)rac_valuesAndChangesForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)observer;
@end
#define RACAble(...) \
metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
(_RACAbleObject(self, __VA_ARGS__)) \
(_RACAbleObject(__VA_ARGS__))
#define _RACAbleObject(object, property) [object rac_signalForKeyPath:@keypath(object, property) observer:self]
#define RACAbleWithStart(...) \
metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
(_RACAbleWithStartObject(self, __VA_ARGS__)) \
(_RACAbleWithStartObject(__VA_ARGS__))
#define _RACAbleWithStartObject(object, property) [object rac_signalWithStartingValueForKeyPath:@keypath(object, property) observer:self]
@interface NSObject (RACPropertySubscribingDeprecated)
+ (RACSignal *)rac_signalFor:(NSObject *)object keyPath:(NSString *)keyPath observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesForKeyPath:observer: or RACObserve() instead.")));
+ (RACSignal *)rac_signalWithStartingValueFor:(NSObject *)object keyPath:(NSString *)keyPath observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesForKeyPath:observer: or RACObserve() instead.")));
+ (RACSignal *)rac_signalWithChangesFor:(NSObject *)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesAndChangesForKeyPath:options:observer: instead.")));
- (RACSignal *)rac_signalForKeyPath:(NSString *)keyPath observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesForKeyPath:observer: or RACObserve() instead.")));
- (RACSignal *)rac_signalWithStartingValueForKeyPath:(NSString *)keyPath observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesForKeyPath:observer: or RACObserve() instead.")));
- (RACDisposable *)rac_deriveProperty:(NSString *)keyPath from:(RACSignal *)signal __attribute__((deprecated("Use -[RACSignal setKeyPath:onObject:] instead")));
@end
@@ -0,0 +1,79 @@
//
// NSObject+RACSelectorSignal.h
// ReactiveCocoa
//
// Created by Josh Abernathy on 3/18/13.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSignal;
/// The domain for any errors originating from -rac_signalForSelector:.
extern NSString * const RACSelectorSignalErrorDomain;
/// -rac_signalForSelector: was going to add a new method implementation for
/// `selector`, but another thread added an implementation before it was able to.
///
/// This will _not_ occur for cases where a method implementation exists before
/// -rac_signalForSelector: is invoked.
extern const NSInteger RACSelectorSignalErrorMethodSwizzlingRace;
@interface NSObject (RACSelectorSignal)
/// Creates a signal associated with the receiver, which will send a tuple of the
/// method's arguments each time the given selector is invoked.
///
/// If the selector is already implemented on the receiver, the existing
/// implementation will be invoked _before_ the signal fires.
///
/// If the selector is not yet implemented on the receiver, the injected
/// implementation will have a `void` return type and accept only object
/// arguments. Invoking the added implementation with non-object values, or
/// expecting a return value, will result in undefined behavior.
///
/// This is useful for changing an event or delegate callback into a signal. For
/// example, on an NSView:
///
/// [[view rac_signalForSelector:@selector(mouseDown:)] subscribeNext:^(RACTuple *args) {
/// NSEvent *event = args.first;
/// NSLog(@"mouse button pressed: %@", event);
/// }];
///
/// selector - The selector for whose invocations are to be observed. If it
/// doesn't exist, it will be implemented to accept object arguments
/// and return void. This cannot have C arrays or unions as arguments
/// or C arrays, unions, structs, complex or vector types as return
/// type.
///
/// Returns a signal which will send a tuple of arguments upon each invocation of
/// the selector, then completes when the receiver is deallocated. `next` events
/// will be sent synchronously from the thread that invoked the method. If
/// a runtime call fails, the signal will send an error in the
/// RACSelectorSignalErrorDomain.
- (RACSignal *)rac_signalForSelector:(SEL)selector;
/// Behaves like -rac_signalForSelector:, but if the selector is not yet
/// implemented on the receiver, its method signature is looked up within
/// `protocol`, and may accept non-object arguments.
///
/// If the selector is not yet implemented and has a return value, the injected
/// method will return all zero bits (equal to `nil`, `NULL`, 0, 0.0f, etc.).
///
/// selector - The selector for whose invocations are to be observed. If it
/// doesn't exist, it will be implemented using information from
/// `protocol`, and may accept non-object arguments and return
/// a value. This cannot have C arrays or unions as arguments or
/// return type.
/// protocol - The protocol in which `selector` is declared. This will be used
/// for type information if the selector is not already implemented on
/// the receiver. This must not be `NULL`, and `selector` must exist
/// in this protocol.
///
/// Returns a signal which will send a tuple of arguments on each invocation of
/// the selector, or an error in RACSelectorSignalErrorDomain if a runtime
/// call fails.
- (RACSignal *)rac_signalForSelector:(SEL)selector fromProtocol:(Protocol *)protocol;
@end
@@ -0,0 +1,20 @@
//
// NSOrderedSet+RACSequenceAdditions.h
// ReactiveCocoa
//
// Created by Justin Spahr-Summers on 2012-10-29.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSequence;
@interface NSOrderedSet (RACSequenceAdditions)
/// Creates and returns a sequence corresponding to the receiver.
///
/// Mutating the receiver will not affect the sequence after it's been created.
@property (nonatomic, copy, readonly) RACSequence *rac_sequence;
@end
@@ -0,0 +1,20 @@
//
// NSSet+RACSequenceAdditions.h
// ReactiveCocoa
//
// Created by Justin Spahr-Summers on 2012-10-29.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSequence;
@interface NSSet (RACSequenceAdditions)
/// Creates and returns a sequence corresponding to the receiver.
///
/// Mutating the receiver will not affect the sequence after it's been created.
@property (nonatomic, copy, readonly) RACSequence *rac_sequence;
@end
@@ -0,0 +1,21 @@
//
// NSString+RACSequenceAdditions.h
// ReactiveCocoa
//
// Created by Justin Spahr-Summers on 2012-10-29.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSequence;
@interface NSString (RACSequenceAdditions)
/// Creates and returns a sequence containing strings corresponding to each
/// composed character sequence in the receiver.
///
/// Mutating the receiver will not affect the sequence after it's been created.
@property (nonatomic, copy, readonly) RACSequence *rac_sequence;
@end
@@ -0,0 +1,22 @@
//
// NSString+RACSupport.h
// ReactiveCocoa
//
// Created by Josh Abernathy on 5/11/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACScheduler;
@class RACSignal;
@interface NSString (RACSupport)
// Reads in the contents of the file using +[NSString stringWithContentsOfURL:usedEncoding:error:].
// Note that encoding won't be valid until the signal completes successfully.
//
// scheduler - cannot be nil.
+ (RACSignal *)rac_readContentsOfURL:(NSURL *)URL usedEncoding:(NSStringEncoding *)encoding scheduler:(RACScheduler *)scheduler;
@end
@@ -0,0 +1,19 @@
//
// NSText+RACSignalSupport.h
// ReactiveCocoa
//
// Created by Justin Spahr-Summers on 2013-03-08.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@class RACSignal;
@interface NSText (RACSignalSupport)
/// Returns a signal which sends the current `string` of the receiver, then the
/// new value any time it changes.
- (RACSignal *)rac_textSignal;
@end
@@ -0,0 +1,25 @@
//
// NSURLConnection+RACSupport.h
// ReactiveCocoa
//
// Created by Justin Spahr-Summers on 2013-10-01.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSignal;
@interface NSURLConnection (RACSupport)
// Lazily loads data for the given request in the background.
//
// request - The URL request to load. This must not be nil.
//
// Returns a signal which will begin loading the request upon each subscription,
// then send a `RACTuple` of the received `NSURLResponse` and downloaded
// `NSData`, and complete on a background thread. If any errors occur, the
// returned signal will error out.
+ (RACSignal *)rac_sendAsynchronousRequest:(NSURLRequest *)request;
@end
@@ -0,0 +1,27 @@
//
// NSUserDefaults+RACSupport.h
// ReactiveCocoa
//
// Created by Matt Diephouse on 12/19/13.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACChannelTerminal;
@interface NSUserDefaults (RACSupport)
/// Creates and returns a terminal for binding the user defaults key.
///
/// **Note:** The value in the user defaults is *asynchronously* updated with
/// values sent to the channel.
///
/// key - The user defaults key to create the channel terminal for.
///
/// Returns a channel terminal that sends the value of the user defaults key
/// upon subscription, sends an updated value whenever the default changes, and
/// updates the default asynchronously with values it receives.
- (RACChannelTerminal *)rac_channelTerminalForKey:(NSString *)key;
@end
@@ -0,0 +1,18 @@
//
// RACBehaviorSubject.h
// ReactiveCocoa
//
// Created by Josh Abernathy on 3/16/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACSubject.h"
/// A behavior subject sends the last value it received when it is subscribed to.
@interface RACBehaviorSubject : RACSubject
/// Creates a new behavior subject with a default value. If it hasn't received
/// any values when it gets subscribed to, it sends the default value.
+ (instancetype)behaviorSubjectWithDefaultValue:(id)value;
@end
@@ -0,0 +1,70 @@
//
// RACChannel.h
// ReactiveCocoa
//
// Created by Uri Baghin on 01/01/2013.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "RACSignal.h"
#import "RACSubscriber.h"
@class RACChannelTerminal;
/// A two-way channel.
///
/// Conceptually, RACChannel can be thought of as a bidirectional connection,
/// composed of two controllable signals that work in parallel.
///
/// For example, when connecting between a view and a model:
///
/// Model View
/// `leadingTerminal` ------> `followingTerminal`
/// `leadingTerminal` <------ `followingTerminal`
///
/// The initial value of the model and all future changes to it are _sent on_ the
/// `leadingTerminal`, and _received by_ subscribers of the `followingTerminal`.
///
/// Likewise, whenever the user changes the value of the view, that value is sent
/// on the `followingTerminal`, and received in the model from the
/// `leadingTerminal`. However, the initial value of the view is not received
/// from the `leadingTerminal` (only future changes).
@interface RACChannel : NSObject
/// The terminal which "leads" the channel, by sending its latest value
/// immediately to new subscribers of the `followingTerminal`.
///
/// New subscribers to this terminal will not receive a starting value, but will
/// receive all future values that are sent to the `followingTerminal`.
@property (nonatomic, strong, readonly) RACChannelTerminal *leadingTerminal;
/// The terminal which "follows" the lead of the other terminal, only sending
/// _future_ values to the subscribers of the `leadingTerminal`.
///
/// The latest value sent to the `leadingTerminal` (if any) will be sent
/// immediately to new subscribers of this terminal, and then all future values
/// as well.
@property (nonatomic, strong, readonly) RACChannelTerminal *followingTerminal;
@end
/// Represents one end of a RACChannel.
///
/// An terminal is similar to a socket or pipe -- it represents one end of
/// a connection (the RACChannel, in this case). Values sent to this terminal
/// will _not_ be received by its subscribers. Instead, the values will be sent
/// to the subscribers of the RACChannel's _other_ terminal.
///
/// For example, when using the `followingTerminal`, _sent_ values can only be
/// _received_ from the `leadingTerminal`, and vice versa.
///
/// To make it easy to terminate a RACChannel, `error` and `completed` events
/// sent to either terminal will be received by the subscribers of _both_
/// terminals.
///
/// Do not instantiate this class directly. Create a RACChannel instead.
@interface RACChannelTerminal : RACSignal <RACSubscriber>
- (id)init __attribute__((unavailable("Instantiate a RACChannel instead")));
@end
@@ -0,0 +1,123 @@
//
// RACCommand.h
// ReactiveCocoa
//
// Created by Josh Abernathy on 3/3/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSignal;
/// The domain for errors originating within `RACCommand`.
extern NSString * const RACCommandErrorDomain;
/// -execute: was invoked while the command was disabled.
extern const NSInteger RACCommandErrorNotEnabled;
/// A `userInfo` key for an error, associated with the `RACCommand` that the
/// error originated from.
///
/// This is included only when the error code is `RACCommandErrorNotEnabled`.
extern NSString * const RACUnderlyingCommandErrorKey;
/// A command is a signal triggered in response to some action, typically
/// UI-related.
@interface RACCommand : NSObject
/// A signal of the signals returned by successful invocations of -execute:
/// (i.e., while the receiver is `enabled`).
///
/// Errors will be automatically caught upon the inner signals, and sent upon
/// `errors` instead. If you _want_ to receive inner errors, use -execute: or
/// -[RACSignal materialize].
///
/// Only executions that begin _after_ subscription will be sent upon this
/// signal. All inner signals will arrive upon the main thread.
@property (nonatomic, strong, readonly) RACSignal *executionSignals;
/// A signal of whether this command is currently executing.
///
/// This will send YES whenever -execute: is invoked and the created signal has
/// not yet terminated. Once all executions have terminated, `executing` will
/// send NO.
///
/// This signal will send its current value upon subscription, and then all
/// future values on the main thread.
@property (nonatomic, strong, readonly) RACSignal *executing;
/// A signal of whether this command is able to execute.
///
/// This will send NO if:
///
/// - The command was created with an `enabledSignal`, and NO is sent upon that
/// signal, or
/// - `allowsConcurrentExecution` is NO and the command has started executing.
///
/// Once the above conditions are no longer met, the signal will send YES.
///
/// This signal will send its current value upon subscription, and then all
/// future values on the main thread.
@property (nonatomic, strong, readonly) RACSignal *enabled;
/// Forwards any errors that occur within signals returned by -execute:.
///
/// When an error occurs on a signal returned from -execute:, this signal will
/// send the associated NSError value as a `next` event (since an `error` event
/// would terminate the stream).
///
/// After subscription, this signal will send all future errors on the main
/// thread.
@property (nonatomic, strong, readonly) RACSignal *errors;
/// Whether the command allows multiple executions to proceed concurrently.
///
/// The default value for this property is NO.
@property (atomic, assign) BOOL allowsConcurrentExecution;
/// Invokes -initWithEnabled:signalBlock: with a nil `enabledSignal`.
- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock;
/// Initializes a command that is conditionally enabled.
///
/// This is the designated initializer for this class.
///
/// enabledSignal - A signal of BOOLs which indicate whether the command should
/// be enabled. `enabled` will be based on the latest value sent
/// from this signal. Before any values are sent, `enabled` will
/// default to YES. This argument may be nil.
/// signalBlock - A block which will map each input value (passed to -execute:)
/// to a signal of work. The returned signal will be multicasted
/// to a replay subject, sent on `executionSignals`, then
/// subscribed to synchronously. Neither the block nor the
/// returned signal may be nil.
- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock;
/// If the receiver is enabled, this method will:
///
/// 1. Invoke the `signalBlock` given at the time of initialization.
/// 2. Multicast the returned signal to a RACReplaySubject.
/// 3. Send the multicasted signal on `executionSignals`.
/// 4. Subscribe (connect) to the original signal on the main thread.
///
/// input - The input value to pass to the receiver's `signalBlock`. This may be
/// nil.
///
/// Returns the multicasted signal, after subscription. If the receiver is not
/// enabled, returns a signal that will send an error with code
/// RACCommandErrorNotEnabled.
- (RACSignal *)execute:(id)input;
@end
@interface RACCommand (Unavailable)
@property (atomic, readonly) BOOL canExecute __attribute__((unavailable("Use the 'enabled' signal instead")));
+ (instancetype)command __attribute__((unavailable("Use -initWithSignalBlock: instead")));
+ (instancetype)commandWithCanExecuteSignal:(RACSignal *)canExecuteSignal __attribute__((unavailable("Use -initWithEnabled:signalBlock: instead")));
- (id)initWithCanExecuteSignal:(RACSignal *)canExecuteSignal __attribute__((unavailable("Use -initWithEnabled:signalBlock: instead")));
- (RACSignal *)addSignalBlock:(RACSignal * (^)(id value))signalBlock __attribute__((unavailable("Pass the signalBlock to -initWithSignalBlock: instead")));
@end
@@ -0,0 +1,48 @@
//
// RACCompoundDisposable.h
// ReactiveCocoa
//
// Created by Josh Abernathy on 11/30/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACDisposable.h"
/// A disposable of disposables. When it is disposed, it disposes of all its
/// contained disposables.
///
/// If -addDisposable: is called after the compound disposable has been disposed
/// of, the given disposable is immediately disposed. This allows a compound
/// disposable to act as a stand-in for a disposable that will be delivered
/// asynchronously.
@interface RACCompoundDisposable : RACDisposable
/// Creates and returns a new compound disposable.
+ (instancetype)compoundDisposable;
/// Creates and returns a new compound disposable containing the given
/// disposables.
+ (instancetype)compoundDisposableWithDisposables:(NSArray *)disposables;
/// Adds the given disposable. If the receiving disposable has already been
/// disposed of, the given disposable is disposed immediately.
///
/// This method is thread-safe.
///
/// disposable - The disposable to add. This may be nil, in which case nothing
/// happens.
- (void)addDisposable:(RACDisposable *)disposable;
/// Removes the specified disposable from the compound disposable (regardless of
/// its disposed status), or does nothing if it's not in the compound disposable.
///
/// This is mainly useful for limiting the memory usage of the compound
/// disposable for long-running operations.
///
/// This method is thread-safe.
///
/// disposable - The disposable to remove. This argument may be nil (to make the
/// use of weak references easier).
- (void)removeDisposable:(RACDisposable *)disposable;
@end
@@ -0,0 +1,35 @@
//
// RACDisposable.h
// ReactiveCocoa
//
// Created by Josh Abernathy on 3/16/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACScopedDisposable;
/// A disposable encapsulates the work necessary to tear down and cleanup a
/// subscription.
@interface RACDisposable : NSObject
/// Whether the receiver has been disposed.
///
/// Use of this property is discouraged, since it may be set to `YES`
/// concurrently at any time.
///
/// This property is not KVO-compliant.
@property (atomic, assign, getter = isDisposed, readonly) BOOL disposed;
+ (instancetype)disposableWithBlock:(void (^)(void))block;
/// Performs the disposal work. Can be called multiple times, though subsequent
/// calls won't do anything.
- (void)dispose;
/// Returns a new disposable which will dispose of this disposable when it gets
/// dealloc'd.
- (RACScopedDisposable *)asScopedDisposable;
@end
@@ -0,0 +1,51 @@
//
// RACEvent.h
// ReactiveCocoa
//
// Created by Justin Spahr-Summers on 2013-01-07.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
/// Describes the type of a RACEvent.
///
/// RACEventTypeCompleted - A `completed` event.
/// RACEventTypeError - An `error` event.
/// RACEventTypeNext - A `next` event.
typedef enum : NSUInteger {
RACEventTypeCompleted,
RACEventTypeError,
RACEventTypeNext
} RACEventType;
/// Represents an event sent by a RACSignal.
///
/// This corresponds to the `Notification` class in Rx.
@interface RACEvent : NSObject <NSCopying>
/// Returns a singleton RACEvent representing the `completed` event.
+ (instancetype)completedEvent;
/// Returns a new event of type RACEventTypeError, containing the given error.
+ (instancetype)eventWithError:(NSError *)error;
/// Returns a new event of type RACEventTypeNext, containing the given value.
+ (instancetype)eventWithValue:(id)value;
/// The type of event represented by the receiver.
@property (nonatomic, assign, readonly) RACEventType eventType;
/// Returns whether the receiver is of type RACEventTypeCompleted or
/// RACEventTypeError.
@property (nonatomic, getter = isFinished, assign, readonly) BOOL finished;
/// The error associated with an event of type RACEventTypeError. This will be
/// nil for all other event types.
@property (nonatomic, strong, readonly) NSError *error;
/// The value associated with an event of type RACEventTypeNext. This will be
/// nil for all other event types.
@property (nonatomic, strong, readonly) id value;
@end
@@ -0,0 +1,19 @@
//
// RACGroupedSignal.h
// ReactiveCocoa
//
// Created by Josh Abernathy on 5/2/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACSubject.h"
/// A grouped signal is used by -[RACSignal groupBy:transform:].
@interface RACGroupedSignal : RACSubject
/// The key shared by the group.
@property (nonatomic, readonly, copy) id<NSCopying> key;
+ (instancetype)signalWithKey:(id<NSCopying>)key;
@end
@@ -0,0 +1,97 @@
//
// RACKVOChannel.h
// ReactiveCocoa
//
// Created by Uri Baghin on 27/12/2012.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACChannel.h"
#import "EXTKeyPathCoding.h"
#import "metamacros.h"
/// Creates a RACKVOChannel to the given key path. When the targeted object
/// deallocates, the channel will complete.
///
/// If RACChannelTo() is used as an expression, it returns a RACChannelTerminal that
/// can be used to watch the specified property for changes, and set new values
/// for it. The terminal will start with the property's current value upon
/// subscription.
///
/// If RACChannelTo() is used on the left-hand side of an assignment, there must a
/// RACChannelTerminal on the right-hand side of the assignment. The two will be
/// subscribed to one another: the property's value is immediately set to the
/// value of the channel terminal on the right-hand side, and subsequent changes
/// to either terminal will be reflected on the other.
///
/// There are two different versions of this macro:
///
/// - RACChannelTo(TARGET, KEYPATH, NILVALUE) will create a channel to the `KEYPATH`
/// of `TARGET`. If the terminal is ever sent a `nil` value, the property will
/// be set to `NILVALUE` instead. `NILVALUE` may itself be `nil` for object
/// properties, but an NSValue should be used for primitive properties, to
/// avoid an exception if `nil` is sent (which might occur if an intermediate
/// object is set to `nil`).
/// - RACChannelTo(TARGET, KEYPATH) is the same as the above, but `NILVALUE` defaults to
/// `nil`.
///
/// Examples
///
/// RACChannelTerminal *integerChannel = RACChannelTo(self, integerProperty, @42);
///
/// // Sets self.integerProperty to 5.
/// [integerChannel sendNext:@5];
///
/// // Logs the current value of self.integerProperty, and all future changes.
/// [integerChannel subscribeNext:^(id value) {
/// NSLog(@"value: %@", value);
/// }];
///
/// // Binds properties to each other, taking the initial value from the right
/// side.
/// RACChannelTo(view, objectProperty) = RACChannelTo(model, objectProperty);
/// RACChannelTo(view, integerProperty, @2) = RACChannelTo(model, integerProperty, @10);
#define RACChannelTo(TARGET, ...) \
metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
(RACChannelTo_(TARGET, __VA_ARGS__, nil)) \
(RACChannelTo_(TARGET, __VA_ARGS__))
/// Do not use this directly. Use the RACChannelTo macro above.
#define RACChannelTo_(TARGET, KEYPATH, NILVALUE) \
[[RACKVOChannel alloc] initWithTarget:(TARGET) keyPath:@keypath(TARGET, KEYPATH) nilValue:(NILVALUE)][@keypath(RACKVOChannel.new, followingTerminal)]
/// A RACChannel that observes a KVO-compliant key path for changes.
@interface RACKVOChannel : RACChannel
/// Initializes a channel that will observe the given object and key path.
///
/// The current value of the key path, and future KVO notifications for the given
/// key path, will be sent to subscribers of the channel's `followingTerminal`.
/// Values sent to the `followingTerminal` will be set at the given key path using
/// key-value coding.
///
/// When the target object deallocates, the channel will complete. Signal errors
/// are considered undefined behavior.
///
/// This is the designated initializer for this class.
///
/// target - The object to bind to.
/// keyPath - The key path to observe and set the value of.
/// nilValue - The value to set at the key path whenever a `nil` value is
/// received. This may be nil when connecting to object properties, but
/// an NSValue should be used for primitive properties, to avoid an
/// exception if `nil` is received (which might occur if an intermediate
/// object is set to `nil`).
- (id)initWithTarget:(__weak NSObject *)target keyPath:(NSString *)keyPath nilValue:(id)nilValue;
- (id)init __attribute__((unavailable("Use -initWithTarget:keyPath:nilValue: instead")));
@end
/// Methods needed for the convenience macro. Do not call explicitly.
@interface RACKVOChannel (RACChannelTo)
- (RACChannelTerminal *)objectForKeyedSubscript:(NSString *)key;
- (void)setObject:(RACChannelTerminal *)otherTerminal forKeyedSubscript:(NSString *)key;
@end
@@ -0,0 +1,48 @@
//
// RACMulticastConnection.h
// ReactiveCocoa
//
// Created by Josh Abernathy on 4/11/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACDisposable;
@class RACSignal;
/// A multicast connection encapsulates the idea of sharing one subscription to a
/// signal to many subscribers. This is most often needed if the subscription to
/// the underlying signal involves side-effects or shouldn't be called more than
/// once.
///
/// The multicasted signal is only subscribed to when
/// -[RACMulticastConnection connect] is called. Until that happens, no values
/// will be sent on `signal`. See -[RACMulticastConnection autoconnect] for how
/// -[RACMulticastConnection connect] can be called automatically.
///
/// Note that you shouldn't create RACMulticastConnection manually. Instead use
/// -[RACSignal publish] or -[RACSignal multicast:].
@interface RACMulticastConnection : NSObject
/// The multicasted signal.
@property (nonatomic, strong, readonly) RACSignal *signal;
/// Connect to the underlying signal by subscribing to it. Calling this multiple
/// times does nothing but return the existing connection's disposable.
///
/// Returns the disposable for the subscription to the multicasted signal.
- (RACDisposable *)connect;
/// Connects to the underlying signal when the returned signal is first
/// subscribed to, and disposes of the subscription to the multicasted signal
/// when the returned signal has no subscribers.
///
/// If new subscribers show up after being disposed, they'll subscribe and then
/// be immediately disposed of. The returned signal will never re-connect to the
/// multicasted signal.
///
/// Returns the autoconnecting signal.
- (RACSignal *)autoconnect;
@end
@@ -0,0 +1,40 @@
//
// RACQueueScheduler+Subclass.h
// ReactiveCocoa
//
// Created by Josh Abernathy on 6/6/13.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "RACQueueScheduler.h"
#import "RACScheduler+Subclass.h"
/// An interface for use by GCD queue-based subclasses.
///
/// See RACScheduler+Subclass.h for subclassing notes.
@interface RACQueueScheduler ()
/// The queue on which blocks are enqueued.
#if OS_OBJECT_HAVE_OBJC_SUPPORT
@property (nonatomic, strong, readonly) dispatch_queue_t queue;
#else
// Swift builds with OS_OBJECT_HAVE_OBJC_SUPPORT=0 for Playgrounds and LLDB :(
@property (nonatomic, assign, readonly) dispatch_queue_t queue;
#endif
/// Initializes the receiver with the name of the scheduler and the queue which
/// the scheduler should use.
///
/// name - The name of the scheduler. If nil, a default name will be used.
/// queue - The queue upon which the receiver should enqueue scheduled blocks.
/// This argument must not be NULL.
///
/// Returns the initialized object.
- (id)initWithName:(NSString *)name queue:(dispatch_queue_t)queue;
/// Converts a date into a GCD time using dispatch_walltime().
///
/// date - The date to convert. This must not be nil.
+ (dispatch_time_t)wallTimeWithDate:(NSDate *)date;
@end
@@ -0,0 +1,18 @@
//
// RACQueueScheduler.h
// ReactiveCocoa
//
// Created by Josh Abernathy on 11/30/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACScheduler.h"
/// An abstract scheduler which asynchronously enqueues all its work to a Grand
/// Central Dispatch queue.
///
/// Because RACQueueScheduler is abstract, it should not be instantiated
/// directly. Create a subclass using the `RACQueueScheduler+Subclass.h`
/// interface and use that instead.
@interface RACQueueScheduler : RACScheduler
@end
@@ -0,0 +1,22 @@
//
// RACReplaySubject.h
// ReactiveCocoa
//
// Created by Josh Abernathy on 3/14/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACSubject.h"
extern const NSUInteger RACReplaySubjectUnlimitedCapacity;
/// A replay subject saves the values it is sent (up to its defined capacity)
/// and resends those to new subscribers. It will also replay an error or
/// completion.
@interface RACReplaySubject : RACSubject
/// Creates a new replay subject with the given capacity. A capacity of
/// RACReplaySubjectUnlimitedCapacity means values are never trimmed.
+ (instancetype)replaySubjectWithCapacity:(NSUInteger)capacity;
@end
@@ -0,0 +1,29 @@
//
// RACScheduler.m
// ReactiveCocoa
//
// Created by Miķelis Vindavs on 5/27/14.
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "RACScheduler.h"
/// An interface for use by subclasses.
///
/// Subclasses should use `-performAsCurrentScheduler:` to do the actual block
/// invocation so that +[RACScheduler currentScheduler] behaves as expected.
///
/// **Note that RACSchedulers are expected to be serial**. Subclasses must honor
/// that contract. See `RACTargetQueueScheduler` for a queue-based scheduler
/// which will enforce the serialization guarantee.
@interface RACScheduler ()
/// Performs the given block with the receiver as the current scheduler for
/// its thread. This should only be called by subclasses to perform their
/// scheduled blocks.
///
/// block - The block to execute. Cannot be NULL.
- (void)performAsCurrentScheduler:(void (^)(void))block;
@end

Some files were not shown because too many files have changed in this diff Show More