├── .gitmodules ├── LICENSE ├── README.md ├── client ├── .gitignore ├── Ares.entitlements ├── Ares.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── AresKit │ ├── AccessToken.swift │ ├── AresKit.h │ ├── Client.swift │ ├── ConnectionManager.swift │ ├── CreatedUser.swift │ ├── CredentialStorage.swift │ ├── DeviceName.swift │ ├── FileTransferContext.swift │ ├── IncomingFileTransfer.swift │ ├── Info.plist │ ├── JSONDeserializable.swift │ ├── OutgoingFileTransfer.swift │ ├── PushNotification.swift │ ├── RegisteredDevice.swift │ └── User.swift ├── Mac │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── green_orb.imageset │ │ │ ├── Contents.json │ │ │ ├── green_orb.png │ │ │ └── green_orb@2x.png │ │ ├── red_orb.imageset │ │ │ ├── Contents.json │ │ │ ├── red_orb.png │ │ │ └── red_orb@2x.png │ │ ├── rocket.imageset │ │ │ ├── 777.png │ │ │ └── Contents.json │ │ └── yellow_orb.imageset │ │ │ ├── Contents.json │ │ │ ├── yellow_orb.png │ │ │ └── yellow_orb@2x.png │ ├── Base.lproj │ │ └── MainMenu.xib │ ├── Info.plist │ ├── LoginWindowController.swift │ ├── LoginWindowController.xib │ └── StatusItemController.swift └── iOS │ ├── APNSManager.swift │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── placeholder.imageset │ │ ├── Contents.json │ │ ├── placeholder.png │ │ ├── placeholder@2x.png │ │ └── placeholder@3x.png │ └── rocket.imageset │ │ ├── 777.png │ │ └── Contents.json │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── LoginViewController.swift │ ├── LoginViewController.xib │ ├── NSDataExtensions.swift │ ├── PreviewViewController.swift │ ├── UIAlertControllerExtensions.swift │ ├── ViewController.swift │ └── ViewController.xib ├── server ├── .gitignore ├── Procfile ├── index.js └── package.json └── steps.png /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "client/External/Alamofire"] 2 | path = client/External/Alamofire 3 | url = https://github.com/Alamofire/Alamofire.git 4 | [submodule "client/External/KeychainAccess"] 5 | path = client/External/KeychainAccess 6 | url = git@github.com:kishikawakatsumi/KeychainAccess.git 7 | [submodule "client/External/KVOController"] 8 | path = client/External/KVOController 9 | url = git@github.com:facebook/KVOController.git 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Indragie Karunaratne 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚀 Ares 2 | **Zero-setup* P2P file transfer between Macs and iOS devices** 3 | 4 | Ares is a service that I built in under 24 hours, winning first place at [HackED 2016](http://eceweek.compeclub.com/hackathon/) during the University of Alberta's ECE Week. 5 | 6 | [**Presentation Slides**](https://speakerdeck.com/indragiek/ares-at-hacked-2016) 7 | 8 | **⚠️ Ares is a proof-of-concept tech demo. It is neither secure nor bug-free enough to be used in production. ⚠️** 9 | 10 | * Zero-setup refers to the lack of setup when sending or receiving a file (e.g. opening the AirDrop tab in a Finder window, opening an email client, browsing for files in Dropbox for iOS, etc.), *not* the initial process of installing and setting up the Ares apps 11 | 12 | ### Motivation 13 | 14 | The existing options for trasferring a file from a Mac to an iOS device (or vice versa) are clumsy or simply unreliable. I Commonly used methods like emailing a file to yourself or using Dropbox are inconvenient because the data has to be uploaded to an intermediary before it can be received by the target device. Apple's own AirDrop is often very unreliable, and the UX is less than ideal. Why do I have to open AirDrop in a Finder tab and *wait* for the devices to discover each other, which sometimes doesn't happen, in order to start a file transfer? 15 | 16 | Ares is a technology demo that shows how much more streamlined the entire process could be. 17 | 18 | ### Using Ares 19 | 20 |

21 | Ares Usage 22 |

23 | 24 | ### Setting Up 25 | 26 | #### Back-end 27 | 28 | I will not be providing a hosted service that can be used directly at this time due to security concerns. You are free to set up your own instance of the back-end, which is a [Node.js](https://nodejs.org/en/) application that was built to be deployed using [Heroku](https://heroku.com) and [MongoLab](https://mongolab.com/). Follow these steps to deploy your own Ares server instance: 29 | 30 | ##### Heroku 31 | 32 | 1. Fork the repository and clone it 33 | 2. [Set up APNS](https://github.com/dkhamsing/apns-guide), copy the `key.pem` and `cert.pem` files (named exactly that way) to the `/server` directory, and commit them 34 | 3. With the [Heroku Toolbelt](https://toolbelt.heroku.com/) installed, run the following commands from the root directory of the repository: 35 | 36 | ``` 37 | $ heroku login 38 | $ heroku create 39 | $ heroku addons:create mongolab:sandbox 40 | $ heroku config:set APP_SECRET=$(uuidgen) 41 | $ git subtree push --prefix server heroku master 42 | ``` 43 | 44 | ##### Running Locally 45 | 46 | ``` 47 | $ cd server 48 | $ npm install 49 | $ export MONGOLAB_URI=$(heroku config:get MONGOLAB_URI) 50 | $ export APP_SECRET=$(heroku config:get APP_SECRET) 51 | $ node index.js 52 | ``` 53 | 54 | Replace the `MONGOLAB_URI` and `APP_SECRET` definitions with your own values if you did not deploy the application on Heroku. 55 | 56 | #### iOS and Mac apps 57 | 58 | Both the iOS and Mac application targets are set up in `client/Ares.xcodeproj`. The iOS-specific code is in the `client/iOS` directory, and the Mac-specific code is in `client/Mac`. `client/AresKit` contains the source for `AresKit`, a cross platform framework containing all of the code shared between the iOS and Mac clients. 59 | 60 | Before building and running the apps, the `DefaultAPIURL` in `client/AresKit/Client.swift` will need to be changed to point to the URL for your Heroku instance, which can be obtained by running `heroku apps:info`. 61 | 62 | ### Future Ideas 63 | 64 | - Bidirectional file transfer: iOS → Mac transfers in addition to the Mac → iOS transfer implemented currently 65 | - A full file manager in the iOS app that lets you view and catalogue files that were previously downloaded over Ares 66 | - Multiple simultaneous file transfers 67 | - Better security in verifying origins and the integrity of delivered payloads 68 | - Fallback to uploading files to a storage server when P2P communication is not available 69 | - iOS action extension for sending content from 3rd party apps via Ares 70 | 71 | ### Contact 72 | 73 | * Indragie Karunaratne 74 | * [@indragie](http://twitter.com/indragie) 75 | * [http://indragie.com](http://indragie.com) 76 | 77 | ### License 78 | 79 | Ares is licensed under the MIT License. See `LICENSE` for more information. 80 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | ## Other 21 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/screenshots 64 | -------------------------------------------------------------------------------- /client/Ares.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | keychain-access-groups 6 | 7 | $(AppIdentifierPrefix)com.indragie.Ares-iOS 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /client/Ares.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 7203D1BE1C5D9F690011E3F0 /* APNSManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7203D1BD1C5D9F690011E3F0 /* APNSManager.swift */; }; 11 | 7203D1C01C5DA0CD0011E3F0 /* NSDataExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7203D1BF1C5DA0CD0011E3F0 /* NSDataExtensions.swift */; }; 12 | 7203D1C31C5DAD580011E3F0 /* ConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7203D1C21C5DAD580011E3F0 /* ConnectionManager.swift */; }; 13 | 7203D1C41C5DAD580011E3F0 /* ConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7203D1C21C5DAD580011E3F0 /* ConnectionManager.swift */; }; 14 | 7203D1C61C5DBA450011E3F0 /* StatusItemController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7203D1C51C5DBA450011E3F0 /* StatusItemController.swift */; }; 15 | 7203D1CA1C5DE20F0011E3F0 /* DeviceName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7203D1C91C5DE20F0011E3F0 /* DeviceName.swift */; }; 16 | 7203D1CB1C5DE20F0011E3F0 /* DeviceName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7203D1C91C5DE20F0011E3F0 /* DeviceName.swift */; }; 17 | 721C793F1C5D41D1008EFF59 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 721C793C1C5D41D1008EFF59 /* AppDelegate.swift */; }; 18 | 721C79401C5D41D1008EFF59 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 721C793D1C5D41D1008EFF59 /* Assets.xcassets */; }; 19 | 721C79441C5D41DD008EFF59 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 721C79421C5D41DD008EFF59 /* MainMenu.xib */; }; 20 | 721C79751C5D428E008EFF59 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 721C79711C5D428E008EFF59 /* AppDelegate.swift */; }; 21 | 721C79761C5D428E008EFF59 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 721C79721C5D428E008EFF59 /* Assets.xcassets */; }; 22 | 721C79781C5D428E008EFF59 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 721C79741C5D428E008EFF59 /* ViewController.swift */; }; 23 | 721C797D1C5D4294008EFF59 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 721C79791C5D4294008EFF59 /* LaunchScreen.storyboard */; }; 24 | 721C79871C5D42DE008EFF59 /* AresKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 721C79861C5D42DE008EFF59 /* AresKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 25 | 721C798B1C5D42DE008EFF59 /* AresKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 721C79841C5D42DE008EFF59 /* AresKit.framework */; }; 26 | 721C798C1C5D42DE008EFF59 /* AresKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 721C79841C5D42DE008EFF59 /* AresKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 27 | 721C799D1C5D4301008EFF59 /* AresKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 721C79961C5D4301008EFF59 /* AresKit.framework */; }; 28 | 721C799E1C5D4301008EFF59 /* AresKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 721C79961C5D4301008EFF59 /* AresKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 29 | 721C79A31C5D4316008EFF59 /* AresKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 721C79861C5D42DE008EFF59 /* AresKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 30 | 721C79A61C5D62A3008EFF59 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 721C79A51C5D62A3008EFF59 /* Client.swift */; }; 31 | 721C79A71C5D62A3008EFF59 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 721C79A51C5D62A3008EFF59 /* Client.swift */; }; 32 | 721C79C11C5D63B0008EFF59 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 721C79B81C5D63A1008EFF59 /* Alamofire.framework */; }; 33 | 721C79C21C5D63B6008EFF59 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 721C79B41C5D63A1008EFF59 /* Alamofire.framework */; }; 34 | 721C79C31C5D63BF008EFF59 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 721C79B81C5D63A1008EFF59 /* Alamofire.framework */; }; 35 | 721C79C41C5D63BF008EFF59 /* Alamofire.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 721C79B81C5D63A1008EFF59 /* Alamofire.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 36 | 721C79C71C5D63C6008EFF59 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 721C79B41C5D63A1008EFF59 /* Alamofire.framework */; }; 37 | 721C79C81C5D63C6008EFF59 /* Alamofire.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 721C79B41C5D63A1008EFF59 /* Alamofire.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 38 | 721C79CC1C5D642F008EFF59 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 721C79CB1C5D642F008EFF59 /* User.swift */; }; 39 | 721C79CD1C5D642F008EFF59 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 721C79CB1C5D642F008EFF59 /* User.swift */; }; 40 | 721C79CF1C5D64F2008EFF59 /* JSONDeserializable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 721C79CE1C5D64F2008EFF59 /* JSONDeserializable.swift */; }; 41 | 721C79D01C5D64F2008EFF59 /* JSONDeserializable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 721C79CE1C5D64F2008EFF59 /* JSONDeserializable.swift */; }; 42 | 721C79D21C5D6514008EFF59 /* AccessToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 721C79D11C5D6514008EFF59 /* AccessToken.swift */; }; 43 | 721C79D31C5D6514008EFF59 /* AccessToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 721C79D11C5D6514008EFF59 /* AccessToken.swift */; }; 44 | 721C79D51C5D7276008EFF59 /* CreatedUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 721C79D41C5D7276008EFF59 /* CreatedUser.swift */; }; 45 | 721C79D61C5D7276008EFF59 /* CreatedUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 721C79D41C5D7276008EFF59 /* CreatedUser.swift */; }; 46 | 721C79DC1C5D78B7008EFF59 /* CredentialStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 721C79DB1C5D78B7008EFF59 /* CredentialStorage.swift */; }; 47 | 721C79DD1C5D78B7008EFF59 /* CredentialStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 721C79DB1C5D78B7008EFF59 /* CredentialStorage.swift */; }; 48 | 721C79F31C5D7969008EFF59 /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 721C79F21C5D7969008EFF59 /* Keychain.swift */; }; 49 | 721C79F41C5D7969008EFF59 /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 721C79F21C5D7969008EFF59 /* Keychain.swift */; }; 50 | 721C79F71C5D8404008EFF59 /* LoginWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 721C79F51C5D8404008EFF59 /* LoginWindowController.swift */; }; 51 | 721C79F81C5D8404008EFF59 /* LoginWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 721C79F61C5D8404008EFF59 /* LoginWindowController.xib */; }; 52 | 721C79FB1C5D843E008EFF59 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 721C79F91C5D843E008EFF59 /* LoginViewController.swift */; }; 53 | 721C79FC1C5D843E008EFF59 /* LoginViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 721C79FA1C5D843E008EFF59 /* LoginViewController.xib */; }; 54 | 721C79FE1C5D89A5008EFF59 /* UIAlertControllerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 721C79FD1C5D89A5008EFF59 /* UIAlertControllerExtensions.swift */; }; 55 | 721C7A001C5D9505008EFF59 /* RegisteredDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 721C79FF1C5D9505008EFF59 /* RegisteredDevice.swift */; }; 56 | 721C7A011C5D9505008EFF59 /* RegisteredDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 721C79FF1C5D9505008EFF59 /* RegisteredDevice.swift */; }; 57 | 7252DD691C5DF12C00E681E1 /* PushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7252DD681C5DF12C00E681E1 /* PushNotification.swift */; }; 58 | 7252DD6A1C5DF12C00E681E1 /* PushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7252DD681C5DF12C00E681E1 /* PushNotification.swift */; }; 59 | 7252DD711C5DFA8B00E681E1 /* IncomingFileTransfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7252DD701C5DFA8B00E681E1 /* IncomingFileTransfer.swift */; }; 60 | 7252DD721C5DFA8B00E681E1 /* IncomingFileTransfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7252DD701C5DFA8B00E681E1 /* IncomingFileTransfer.swift */; }; 61 | 7252DD741C5DFC5D00E681E1 /* FileTransferContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7252DD731C5DFC5D00E681E1 /* FileTransferContext.swift */; }; 62 | 7252DD751C5DFC5D00E681E1 /* FileTransferContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7252DD731C5DFC5D00E681E1 /* FileTransferContext.swift */; }; 63 | 7252DD771C5DFD4400E681E1 /* OutgoingFileTransfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7252DD761C5DFD4400E681E1 /* OutgoingFileTransfer.swift */; }; 64 | 7252DD781C5DFD4400E681E1 /* OutgoingFileTransfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7252DD761C5DFD4400E681E1 /* OutgoingFileTransfer.swift */; }; 65 | 7252DD7A1C5E0F5A00E681E1 /* ViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7252DD791C5E0F5A00E681E1 /* ViewController.xib */; }; 66 | 7252DD841C5E127A00E681E1 /* KVOController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7252DD811C5E126D00E681E1 /* KVOController.framework */; }; 67 | 7252DD851C5E127A00E681E1 /* KVOController.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7252DD811C5E126D00E681E1 /* KVOController.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 68 | 7252DD891C5E14AA00E681E1 /* PreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7252DD881C5E14AA00E681E1 /* PreviewViewController.swift */; }; 69 | /* End PBXBuildFile section */ 70 | 71 | /* Begin PBXContainerItemProxy section */ 72 | 721C79891C5D42DE008EFF59 /* PBXContainerItemProxy */ = { 73 | isa = PBXContainerItemProxy; 74 | containerPortal = 721C79241C5D418C008EFF59 /* Project object */; 75 | proxyType = 1; 76 | remoteGlobalIDString = 721C79831C5D42DE008EFF59; 77 | remoteInfo = AresKit; 78 | }; 79 | 721C799B1C5D4301008EFF59 /* PBXContainerItemProxy */ = { 80 | isa = PBXContainerItemProxy; 81 | containerPortal = 721C79241C5D418C008EFF59 /* Project object */; 82 | proxyType = 1; 83 | remoteGlobalIDString = 721C79951C5D4301008EFF59; 84 | remoteInfo = "AresKit-iOS"; 85 | }; 86 | 721C79B31C5D63A1008EFF59 /* PBXContainerItemProxy */ = { 87 | isa = PBXContainerItemProxy; 88 | containerPortal = 721C79A91C5D63A1008EFF59 /* Alamofire.xcodeproj */; 89 | proxyType = 2; 90 | remoteGlobalIDString = F8111E3319A95C8B0040E7D1; 91 | remoteInfo = "Alamofire iOS"; 92 | }; 93 | 721C79B51C5D63A1008EFF59 /* PBXContainerItemProxy */ = { 94 | isa = PBXContainerItemProxy; 95 | containerPortal = 721C79A91C5D63A1008EFF59 /* Alamofire.xcodeproj */; 96 | proxyType = 2; 97 | remoteGlobalIDString = F8111E3E19A95C8B0040E7D1; 98 | remoteInfo = "Alamofire iOS Tests"; 99 | }; 100 | 721C79B71C5D63A1008EFF59 /* PBXContainerItemProxy */ = { 101 | isa = PBXContainerItemProxy; 102 | containerPortal = 721C79A91C5D63A1008EFF59 /* Alamofire.xcodeproj */; 103 | proxyType = 2; 104 | remoteGlobalIDString = 4DD67C0B1A5C55C900ED2280; 105 | remoteInfo = "Alamofire OSX"; 106 | }; 107 | 721C79B91C5D63A1008EFF59 /* PBXContainerItemProxy */ = { 108 | isa = PBXContainerItemProxy; 109 | containerPortal = 721C79A91C5D63A1008EFF59 /* Alamofire.xcodeproj */; 110 | proxyType = 2; 111 | remoteGlobalIDString = F829C6B21A7A94F100A2CD59; 112 | remoteInfo = "Alamofire OSX Tests"; 113 | }; 114 | 721C79BB1C5D63A1008EFF59 /* PBXContainerItemProxy */ = { 115 | isa = PBXContainerItemProxy; 116 | containerPortal = 721C79A91C5D63A1008EFF59 /* Alamofire.xcodeproj */; 117 | proxyType = 2; 118 | remoteGlobalIDString = 4CF626EF1BA7CB3E0011A099; 119 | remoteInfo = "Alamofire tvOS"; 120 | }; 121 | 721C79BD1C5D63A1008EFF59 /* PBXContainerItemProxy */ = { 122 | isa = PBXContainerItemProxy; 123 | containerPortal = 721C79A91C5D63A1008EFF59 /* Alamofire.xcodeproj */; 124 | proxyType = 2; 125 | remoteGlobalIDString = 4CF626F81BA7CB3E0011A099; 126 | remoteInfo = "Alamofire tvOS Tests"; 127 | }; 128 | 721C79BF1C5D63A1008EFF59 /* PBXContainerItemProxy */ = { 129 | isa = PBXContainerItemProxy; 130 | containerPortal = 721C79A91C5D63A1008EFF59 /* Alamofire.xcodeproj */; 131 | proxyType = 2; 132 | remoteGlobalIDString = E4202FE01B667AA100C997FB; 133 | remoteInfo = "Alamofire watchOS"; 134 | }; 135 | 721C79C51C5D63BF008EFF59 /* PBXContainerItemProxy */ = { 136 | isa = PBXContainerItemProxy; 137 | containerPortal = 721C79A91C5D63A1008EFF59 /* Alamofire.xcodeproj */; 138 | proxyType = 1; 139 | remoteGlobalIDString = 4DD67C0A1A5C55C900ED2280; 140 | remoteInfo = "Alamofire OSX"; 141 | }; 142 | 721C79C91C5D63C6008EFF59 /* PBXContainerItemProxy */ = { 143 | isa = PBXContainerItemProxy; 144 | containerPortal = 721C79A91C5D63A1008EFF59 /* Alamofire.xcodeproj */; 145 | proxyType = 1; 146 | remoteGlobalIDString = F8111E3219A95C8B0040E7D1; 147 | remoteInfo = "Alamofire iOS"; 148 | }; 149 | 7252DD801C5E126D00E681E1 /* PBXContainerItemProxy */ = { 150 | isa = PBXContainerItemProxy; 151 | containerPortal = 7252DD7B1C5E126D00E681E1 /* DynamicFramework.xcodeproj */; 152 | proxyType = 2; 153 | remoteGlobalIDString = D719F2981B73962E0090C2FB; 154 | remoteInfo = "KVOController-iOS"; 155 | }; 156 | 7252DD821C5E126D00E681E1 /* PBXContainerItemProxy */ = { 157 | isa = PBXContainerItemProxy; 158 | containerPortal = 7252DD7B1C5E126D00E681E1 /* DynamicFramework.xcodeproj */; 159 | proxyType = 2; 160 | remoteGlobalIDString = D719F2BD1B7396AF0090C2FB; 161 | remoteInfo = "KVOController-OSX"; 162 | }; 163 | 7252DD861C5E127A00E681E1 /* PBXContainerItemProxy */ = { 164 | isa = PBXContainerItemProxy; 165 | containerPortal = 7252DD7B1C5E126D00E681E1 /* DynamicFramework.xcodeproj */; 166 | proxyType = 1; 167 | remoteGlobalIDString = D719F2971B73962E0090C2FB; 168 | remoteInfo = "KVOController-iOS"; 169 | }; 170 | /* End PBXContainerItemProxy section */ 171 | 172 | /* Begin PBXCopyFilesBuildPhase section */ 173 | 721C79901C5D42DE008EFF59 /* Embed Frameworks */ = { 174 | isa = PBXCopyFilesBuildPhase; 175 | buildActionMask = 2147483647; 176 | dstPath = ""; 177 | dstSubfolderSpec = 10; 178 | files = ( 179 | 721C798C1C5D42DE008EFF59 /* AresKit.framework in Embed Frameworks */, 180 | 721C79C41C5D63BF008EFF59 /* Alamofire.framework in Embed Frameworks */, 181 | ); 182 | name = "Embed Frameworks"; 183 | runOnlyForDeploymentPostprocessing = 0; 184 | }; 185 | 721C79A21C5D4302008EFF59 /* Embed Frameworks */ = { 186 | isa = PBXCopyFilesBuildPhase; 187 | buildActionMask = 2147483647; 188 | dstPath = ""; 189 | dstSubfolderSpec = 10; 190 | files = ( 191 | 7252DD851C5E127A00E681E1 /* KVOController.framework in Embed Frameworks */, 192 | 721C799E1C5D4301008EFF59 /* AresKit.framework in Embed Frameworks */, 193 | 721C79C81C5D63C6008EFF59 /* Alamofire.framework in Embed Frameworks */, 194 | ); 195 | name = "Embed Frameworks"; 196 | runOnlyForDeploymentPostprocessing = 0; 197 | }; 198 | /* End PBXCopyFilesBuildPhase section */ 199 | 200 | /* Begin PBXFileReference section */ 201 | 7203D1BD1C5D9F690011E3F0 /* APNSManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = APNSManager.swift; path = iOS/APNSManager.swift; sourceTree = SOURCE_ROOT; }; 202 | 7203D1BF1C5DA0CD0011E3F0 /* NSDataExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NSDataExtensions.swift; path = iOS/NSDataExtensions.swift; sourceTree = SOURCE_ROOT; }; 203 | 7203D1C11C5DA8F10011E3F0 /* Ares.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = Ares.entitlements; sourceTree = ""; }; 204 | 7203D1C21C5DAD580011E3F0 /* ConnectionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionManager.swift; sourceTree = ""; }; 205 | 7203D1C51C5DBA450011E3F0 /* StatusItemController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StatusItemController.swift; path = Mac/StatusItemController.swift; sourceTree = SOURCE_ROOT; }; 206 | 7203D1C91C5DE20F0011E3F0 /* DeviceName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceName.swift; sourceTree = ""; }; 207 | 721C792C1C5D418C008EFF59 /* Ares.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ares.app; sourceTree = BUILT_PRODUCTS_DIR; }; 208 | 721C793C1C5D41D1008EFF59 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = Mac/AppDelegate.swift; sourceTree = SOURCE_ROOT; }; 209 | 721C793D1C5D41D1008EFF59 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Mac/Assets.xcassets; sourceTree = SOURCE_ROOT; }; 210 | 721C793E1C5D41D1008EFF59 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Mac/Info.plist; sourceTree = SOURCE_ROOT; }; 211 | 721C79431C5D41DD008EFF59 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Mac/Base.lproj/MainMenu.xib; sourceTree = SOURCE_ROOT; }; 212 | 721C795F1C5D426C008EFF59 /* Ares.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ares.app; sourceTree = BUILT_PRODUCTS_DIR; }; 213 | 721C79711C5D428E008EFF59 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = iOS/AppDelegate.swift; sourceTree = SOURCE_ROOT; }; 214 | 721C79721C5D428E008EFF59 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = iOS/Assets.xcassets; sourceTree = SOURCE_ROOT; }; 215 | 721C79731C5D428E008EFF59 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = iOS/Info.plist; sourceTree = SOURCE_ROOT; }; 216 | 721C79741C5D428E008EFF59 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ViewController.swift; path = iOS/ViewController.swift; sourceTree = SOURCE_ROOT; }; 217 | 721C797A1C5D4294008EFF59 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = iOS/Base.lproj/LaunchScreen.storyboard; sourceTree = SOURCE_ROOT; }; 218 | 721C79841C5D42DE008EFF59 /* AresKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AresKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 219 | 721C79861C5D42DE008EFF59 /* AresKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AresKit.h; sourceTree = ""; }; 220 | 721C79881C5D42DE008EFF59 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 221 | 721C79961C5D4301008EFF59 /* AresKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AresKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 222 | 721C79A51C5D62A3008EFF59 /* Client.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = ""; }; 223 | 721C79A91C5D63A1008EFF59 /* Alamofire.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Alamofire.xcodeproj; path = External/Alamofire/Alamofire.xcodeproj; sourceTree = ""; }; 224 | 721C79CB1C5D642F008EFF59 /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; 225 | 721C79CE1C5D64F2008EFF59 /* JSONDeserializable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONDeserializable.swift; sourceTree = ""; }; 226 | 721C79D11C5D6514008EFF59 /* AccessToken.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessToken.swift; sourceTree = ""; }; 227 | 721C79D41C5D7276008EFF59 /* CreatedUser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreatedUser.swift; sourceTree = ""; }; 228 | 721C79DB1C5D78B7008EFF59 /* CredentialStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CredentialStorage.swift; sourceTree = ""; }; 229 | 721C79F21C5D7969008EFF59 /* Keychain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Keychain.swift; path = External/KeychainAccess/Lib/KeychainAccess/Keychain.swift; sourceTree = SOURCE_ROOT; }; 230 | 721C79F51C5D8404008EFF59 /* LoginWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LoginWindowController.swift; path = Mac/LoginWindowController.swift; sourceTree = SOURCE_ROOT; }; 231 | 721C79F61C5D8404008EFF59 /* LoginWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = LoginWindowController.xib; path = Mac/LoginWindowController.xib; sourceTree = SOURCE_ROOT; }; 232 | 721C79F91C5D843E008EFF59 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LoginViewController.swift; path = iOS/LoginViewController.swift; sourceTree = SOURCE_ROOT; }; 233 | 721C79FA1C5D843E008EFF59 /* LoginViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = LoginViewController.xib; path = iOS/LoginViewController.xib; sourceTree = SOURCE_ROOT; }; 234 | 721C79FD1C5D89A5008EFF59 /* UIAlertControllerExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UIAlertControllerExtensions.swift; path = iOS/UIAlertControllerExtensions.swift; sourceTree = SOURCE_ROOT; }; 235 | 721C79FF1C5D9505008EFF59 /* RegisteredDevice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegisteredDevice.swift; sourceTree = ""; }; 236 | 7252DD681C5DF12C00E681E1 /* PushNotification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushNotification.swift; sourceTree = ""; }; 237 | 7252DD701C5DFA8B00E681E1 /* IncomingFileTransfer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IncomingFileTransfer.swift; sourceTree = ""; }; 238 | 7252DD731C5DFC5D00E681E1 /* FileTransferContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileTransferContext.swift; sourceTree = ""; }; 239 | 7252DD761C5DFD4400E681E1 /* OutgoingFileTransfer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingFileTransfer.swift; sourceTree = ""; }; 240 | 7252DD791C5E0F5A00E681E1 /* ViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = ViewController.xib; path = iOS/ViewController.xib; sourceTree = SOURCE_ROOT; }; 241 | 7252DD7B1C5E126D00E681E1 /* DynamicFramework.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = DynamicFramework.xcodeproj; path = External/KVOController/DynamicFramework.xcodeproj; sourceTree = ""; }; 242 | 7252DD881C5E14AA00E681E1 /* PreviewViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PreviewViewController.swift; path = iOS/PreviewViewController.swift; sourceTree = SOURCE_ROOT; }; 243 | /* End PBXFileReference section */ 244 | 245 | /* Begin PBXFrameworksBuildPhase section */ 246 | 721C79291C5D418C008EFF59 /* Frameworks */ = { 247 | isa = PBXFrameworksBuildPhase; 248 | buildActionMask = 2147483647; 249 | files = ( 250 | 721C798B1C5D42DE008EFF59 /* AresKit.framework in Frameworks */, 251 | 721C79C31C5D63BF008EFF59 /* Alamofire.framework in Frameworks */, 252 | ); 253 | runOnlyForDeploymentPostprocessing = 0; 254 | }; 255 | 721C795C1C5D426C008EFF59 /* Frameworks */ = { 256 | isa = PBXFrameworksBuildPhase; 257 | buildActionMask = 2147483647; 258 | files = ( 259 | 7252DD841C5E127A00E681E1 /* KVOController.framework in Frameworks */, 260 | 721C799D1C5D4301008EFF59 /* AresKit.framework in Frameworks */, 261 | 721C79C71C5D63C6008EFF59 /* Alamofire.framework in Frameworks */, 262 | ); 263 | runOnlyForDeploymentPostprocessing = 0; 264 | }; 265 | 721C79801C5D42DE008EFF59 /* Frameworks */ = { 266 | isa = PBXFrameworksBuildPhase; 267 | buildActionMask = 2147483647; 268 | files = ( 269 | 721C79C11C5D63B0008EFF59 /* Alamofire.framework in Frameworks */, 270 | ); 271 | runOnlyForDeploymentPostprocessing = 0; 272 | }; 273 | 721C79921C5D4301008EFF59 /* Frameworks */ = { 274 | isa = PBXFrameworksBuildPhase; 275 | buildActionMask = 2147483647; 276 | files = ( 277 | 721C79C21C5D63B6008EFF59 /* Alamofire.framework in Frameworks */, 278 | ); 279 | runOnlyForDeploymentPostprocessing = 0; 280 | }; 281 | /* End PBXFrameworksBuildPhase section */ 282 | 283 | /* Begin PBXGroup section */ 284 | 721C79231C5D418C008EFF59 = { 285 | isa = PBXGroup; 286 | children = ( 287 | 7203D1C11C5DA8F10011E3F0 /* Ares.entitlements */, 288 | 721C79A81C5D6397008EFF59 /* External */, 289 | 721C792E1C5D418C008EFF59 /* Mac */, 290 | 721C79601C5D426C008EFF59 /* iOS */, 291 | 721C79851C5D42DE008EFF59 /* AresKit */, 292 | 721C792D1C5D418C008EFF59 /* Products */, 293 | ); 294 | sourceTree = ""; 295 | }; 296 | 721C792D1C5D418C008EFF59 /* Products */ = { 297 | isa = PBXGroup; 298 | children = ( 299 | 721C792C1C5D418C008EFF59 /* Ares.app */, 300 | 721C795F1C5D426C008EFF59 /* Ares.app */, 301 | 721C79841C5D42DE008EFF59 /* AresKit.framework */, 302 | 721C79961C5D4301008EFF59 /* AresKit.framework */, 303 | ); 304 | name = Products; 305 | sourceTree = ""; 306 | }; 307 | 721C792E1C5D418C008EFF59 /* Mac */ = { 308 | isa = PBXGroup; 309 | children = ( 310 | 721C793C1C5D41D1008EFF59 /* AppDelegate.swift */, 311 | 721C793D1C5D41D1008EFF59 /* Assets.xcassets */, 312 | 721C793E1C5D41D1008EFF59 /* Info.plist */, 313 | 721C79421C5D41DD008EFF59 /* MainMenu.xib */, 314 | 721C79F51C5D8404008EFF59 /* LoginWindowController.swift */, 315 | 721C79F61C5D8404008EFF59 /* LoginWindowController.xib */, 316 | 7203D1C51C5DBA450011E3F0 /* StatusItemController.swift */, 317 | ); 318 | name = Mac; 319 | path = Ares; 320 | sourceTree = ""; 321 | }; 322 | 721C79601C5D426C008EFF59 /* iOS */ = { 323 | isa = PBXGroup; 324 | children = ( 325 | 721C79711C5D428E008EFF59 /* AppDelegate.swift */, 326 | 721C79721C5D428E008EFF59 /* Assets.xcassets */, 327 | 721C79731C5D428E008EFF59 /* Info.plist */, 328 | 721C79741C5D428E008EFF59 /* ViewController.swift */, 329 | 721C79791C5D4294008EFF59 /* LaunchScreen.storyboard */, 330 | 721C79F91C5D843E008EFF59 /* LoginViewController.swift */, 331 | 721C79FA1C5D843E008EFF59 /* LoginViewController.xib */, 332 | 721C79FD1C5D89A5008EFF59 /* UIAlertControllerExtensions.swift */, 333 | 7203D1BD1C5D9F690011E3F0 /* APNSManager.swift */, 334 | 7203D1BF1C5DA0CD0011E3F0 /* NSDataExtensions.swift */, 335 | 7252DD791C5E0F5A00E681E1 /* ViewController.xib */, 336 | 7252DD881C5E14AA00E681E1 /* PreviewViewController.swift */, 337 | ); 338 | name = iOS; 339 | path = "Ares-iOS"; 340 | sourceTree = ""; 341 | }; 342 | 721C79851C5D42DE008EFF59 /* AresKit */ = { 343 | isa = PBXGroup; 344 | children = ( 345 | 721C79861C5D42DE008EFF59 /* AresKit.h */, 346 | 721C79881C5D42DE008EFF59 /* Info.plist */, 347 | 721C79A51C5D62A3008EFF59 /* Client.swift */, 348 | 721C79CB1C5D642F008EFF59 /* User.swift */, 349 | 721C79CE1C5D64F2008EFF59 /* JSONDeserializable.swift */, 350 | 721C79D11C5D6514008EFF59 /* AccessToken.swift */, 351 | 721C79D41C5D7276008EFF59 /* CreatedUser.swift */, 352 | 721C79DB1C5D78B7008EFF59 /* CredentialStorage.swift */, 353 | 721C79F21C5D7969008EFF59 /* Keychain.swift */, 354 | 721C79FF1C5D9505008EFF59 /* RegisteredDevice.swift */, 355 | 7203D1C21C5DAD580011E3F0 /* ConnectionManager.swift */, 356 | 7203D1C91C5DE20F0011E3F0 /* DeviceName.swift */, 357 | 7252DD681C5DF12C00E681E1 /* PushNotification.swift */, 358 | 7252DD701C5DFA8B00E681E1 /* IncomingFileTransfer.swift */, 359 | 7252DD761C5DFD4400E681E1 /* OutgoingFileTransfer.swift */, 360 | 7252DD731C5DFC5D00E681E1 /* FileTransferContext.swift */, 361 | ); 362 | path = AresKit; 363 | sourceTree = ""; 364 | }; 365 | 721C79A81C5D6397008EFF59 /* External */ = { 366 | isa = PBXGroup; 367 | children = ( 368 | 7252DD7B1C5E126D00E681E1 /* DynamicFramework.xcodeproj */, 369 | 721C79A91C5D63A1008EFF59 /* Alamofire.xcodeproj */, 370 | ); 371 | name = External; 372 | sourceTree = ""; 373 | }; 374 | 721C79AA1C5D63A1008EFF59 /* Products */ = { 375 | isa = PBXGroup; 376 | children = ( 377 | 721C79B41C5D63A1008EFF59 /* Alamofire.framework */, 378 | 721C79B61C5D63A1008EFF59 /* Alamofire iOS Tests.xctest */, 379 | 721C79B81C5D63A1008EFF59 /* Alamofire.framework */, 380 | 721C79BA1C5D63A1008EFF59 /* Alamofire OSX Tests.xctest */, 381 | 721C79BC1C5D63A1008EFF59 /* Alamofire.framework */, 382 | 721C79BE1C5D63A1008EFF59 /* Alamofire tvOS Tests.xctest */, 383 | 721C79C01C5D63A1008EFF59 /* Alamofire.framework */, 384 | ); 385 | name = Products; 386 | sourceTree = ""; 387 | }; 388 | 7252DD7C1C5E126D00E681E1 /* Products */ = { 389 | isa = PBXGroup; 390 | children = ( 391 | 7252DD811C5E126D00E681E1 /* KVOController.framework */, 392 | 7252DD831C5E126D00E681E1 /* KVOController.framework */, 393 | ); 394 | name = Products; 395 | sourceTree = ""; 396 | }; 397 | /* End PBXGroup section */ 398 | 399 | /* Begin PBXHeadersBuildPhase section */ 400 | 721C79811C5D42DE008EFF59 /* Headers */ = { 401 | isa = PBXHeadersBuildPhase; 402 | buildActionMask = 2147483647; 403 | files = ( 404 | 721C79871C5D42DE008EFF59 /* AresKit.h in Headers */, 405 | ); 406 | runOnlyForDeploymentPostprocessing = 0; 407 | }; 408 | 721C79931C5D4301008EFF59 /* Headers */ = { 409 | isa = PBXHeadersBuildPhase; 410 | buildActionMask = 2147483647; 411 | files = ( 412 | 721C79A31C5D4316008EFF59 /* AresKit.h in Headers */, 413 | ); 414 | runOnlyForDeploymentPostprocessing = 0; 415 | }; 416 | /* End PBXHeadersBuildPhase section */ 417 | 418 | /* Begin PBXNativeTarget section */ 419 | 721C792B1C5D418C008EFF59 /* Ares-Mac */ = { 420 | isa = PBXNativeTarget; 421 | buildConfigurationList = 721C79391C5D418C008EFF59 /* Build configuration list for PBXNativeTarget "Ares-Mac" */; 422 | buildPhases = ( 423 | 721C79281C5D418C008EFF59 /* Sources */, 424 | 721C79291C5D418C008EFF59 /* Frameworks */, 425 | 721C792A1C5D418C008EFF59 /* Resources */, 426 | 721C79901C5D42DE008EFF59 /* Embed Frameworks */, 427 | ); 428 | buildRules = ( 429 | ); 430 | dependencies = ( 431 | 721C798A1C5D42DE008EFF59 /* PBXTargetDependency */, 432 | 721C79C61C5D63BF008EFF59 /* PBXTargetDependency */, 433 | ); 434 | name = "Ares-Mac"; 435 | productName = Ares; 436 | productReference = 721C792C1C5D418C008EFF59 /* Ares.app */; 437 | productType = "com.apple.product-type.application"; 438 | }; 439 | 721C795E1C5D426C008EFF59 /* Ares-iOS */ = { 440 | isa = PBXNativeTarget; 441 | buildConfigurationList = 721C796E1C5D426C008EFF59 /* Build configuration list for PBXNativeTarget "Ares-iOS" */; 442 | buildPhases = ( 443 | 721C795B1C5D426C008EFF59 /* Sources */, 444 | 721C795C1C5D426C008EFF59 /* Frameworks */, 445 | 721C795D1C5D426C008EFF59 /* Resources */, 446 | 721C79A21C5D4302008EFF59 /* Embed Frameworks */, 447 | ); 448 | buildRules = ( 449 | ); 450 | dependencies = ( 451 | 721C799C1C5D4301008EFF59 /* PBXTargetDependency */, 452 | 721C79CA1C5D63C6008EFF59 /* PBXTargetDependency */, 453 | 7252DD871C5E127A00E681E1 /* PBXTargetDependency */, 454 | ); 455 | name = "Ares-iOS"; 456 | productName = "Ares-iOS"; 457 | productReference = 721C795F1C5D426C008EFF59 /* Ares.app */; 458 | productType = "com.apple.product-type.application"; 459 | }; 460 | 721C79831C5D42DE008EFF59 /* AresKit-Mac */ = { 461 | isa = PBXNativeTarget; 462 | buildConfigurationList = 721C798D1C5D42DE008EFF59 /* Build configuration list for PBXNativeTarget "AresKit-Mac" */; 463 | buildPhases = ( 464 | 721C797F1C5D42DE008EFF59 /* Sources */, 465 | 721C79801C5D42DE008EFF59 /* Frameworks */, 466 | 721C79811C5D42DE008EFF59 /* Headers */, 467 | 721C79821C5D42DE008EFF59 /* Resources */, 468 | ); 469 | buildRules = ( 470 | ); 471 | dependencies = ( 472 | ); 473 | name = "AresKit-Mac"; 474 | productName = AresKit; 475 | productReference = 721C79841C5D42DE008EFF59 /* AresKit.framework */; 476 | productType = "com.apple.product-type.framework"; 477 | }; 478 | 721C79951C5D4301008EFF59 /* AresKit-iOS */ = { 479 | isa = PBXNativeTarget; 480 | buildConfigurationList = 721C799F1C5D4302008EFF59 /* Build configuration list for PBXNativeTarget "AresKit-iOS" */; 481 | buildPhases = ( 482 | 721C79911C5D4301008EFF59 /* Sources */, 483 | 721C79921C5D4301008EFF59 /* Frameworks */, 484 | 721C79931C5D4301008EFF59 /* Headers */, 485 | 721C79941C5D4301008EFF59 /* Resources */, 486 | ); 487 | buildRules = ( 488 | ); 489 | dependencies = ( 490 | ); 491 | name = "AresKit-iOS"; 492 | productName = "AresKit-iOS"; 493 | productReference = 721C79961C5D4301008EFF59 /* AresKit.framework */; 494 | productType = "com.apple.product-type.framework"; 495 | }; 496 | /* End PBXNativeTarget section */ 497 | 498 | /* Begin PBXProject section */ 499 | 721C79241C5D418C008EFF59 /* Project object */ = { 500 | isa = PBXProject; 501 | attributes = { 502 | LastSwiftUpdateCheck = 0730; 503 | LastUpgradeCheck = 0730; 504 | ORGANIZATIONNAME = "Indragie Karunaratne"; 505 | TargetAttributes = { 506 | 721C792B1C5D418C008EFF59 = { 507 | CreatedOnToolsVersion = 7.3; 508 | DevelopmentTeam = H73VKH7W9W; 509 | }; 510 | 721C795E1C5D426C008EFF59 = { 511 | CreatedOnToolsVersion = 7.3; 512 | DevelopmentTeam = H73VKH7W9W; 513 | SystemCapabilities = { 514 | com.apple.Keychain = { 515 | enabled = 1; 516 | }; 517 | }; 518 | }; 519 | 721C79831C5D42DE008EFF59 = { 520 | CreatedOnToolsVersion = 7.3; 521 | }; 522 | 721C79951C5D4301008EFF59 = { 523 | CreatedOnToolsVersion = 7.3; 524 | }; 525 | }; 526 | }; 527 | buildConfigurationList = 721C79271C5D418C008EFF59 /* Build configuration list for PBXProject "Ares" */; 528 | compatibilityVersion = "Xcode 3.2"; 529 | developmentRegion = English; 530 | hasScannedForEncodings = 0; 531 | knownRegions = ( 532 | en, 533 | Base, 534 | ); 535 | mainGroup = 721C79231C5D418C008EFF59; 536 | productRefGroup = 721C792D1C5D418C008EFF59 /* Products */; 537 | projectDirPath = ""; 538 | projectReferences = ( 539 | { 540 | ProductGroup = 721C79AA1C5D63A1008EFF59 /* Products */; 541 | ProjectRef = 721C79A91C5D63A1008EFF59 /* Alamofire.xcodeproj */; 542 | }, 543 | { 544 | ProductGroup = 7252DD7C1C5E126D00E681E1 /* Products */; 545 | ProjectRef = 7252DD7B1C5E126D00E681E1 /* DynamicFramework.xcodeproj */; 546 | }, 547 | ); 548 | projectRoot = ""; 549 | targets = ( 550 | 721C792B1C5D418C008EFF59 /* Ares-Mac */, 551 | 721C795E1C5D426C008EFF59 /* Ares-iOS */, 552 | 721C79831C5D42DE008EFF59 /* AresKit-Mac */, 553 | 721C79951C5D4301008EFF59 /* AresKit-iOS */, 554 | ); 555 | }; 556 | /* End PBXProject section */ 557 | 558 | /* Begin PBXReferenceProxy section */ 559 | 721C79B41C5D63A1008EFF59 /* Alamofire.framework */ = { 560 | isa = PBXReferenceProxy; 561 | fileType = wrapper.framework; 562 | path = Alamofire.framework; 563 | remoteRef = 721C79B31C5D63A1008EFF59 /* PBXContainerItemProxy */; 564 | sourceTree = BUILT_PRODUCTS_DIR; 565 | }; 566 | 721C79B61C5D63A1008EFF59 /* Alamofire iOS Tests.xctest */ = { 567 | isa = PBXReferenceProxy; 568 | fileType = wrapper.cfbundle; 569 | path = "Alamofire iOS Tests.xctest"; 570 | remoteRef = 721C79B51C5D63A1008EFF59 /* PBXContainerItemProxy */; 571 | sourceTree = BUILT_PRODUCTS_DIR; 572 | }; 573 | 721C79B81C5D63A1008EFF59 /* Alamofire.framework */ = { 574 | isa = PBXReferenceProxy; 575 | fileType = wrapper.framework; 576 | path = Alamofire.framework; 577 | remoteRef = 721C79B71C5D63A1008EFF59 /* PBXContainerItemProxy */; 578 | sourceTree = BUILT_PRODUCTS_DIR; 579 | }; 580 | 721C79BA1C5D63A1008EFF59 /* Alamofire OSX Tests.xctest */ = { 581 | isa = PBXReferenceProxy; 582 | fileType = wrapper.cfbundle; 583 | path = "Alamofire OSX Tests.xctest"; 584 | remoteRef = 721C79B91C5D63A1008EFF59 /* PBXContainerItemProxy */; 585 | sourceTree = BUILT_PRODUCTS_DIR; 586 | }; 587 | 721C79BC1C5D63A1008EFF59 /* Alamofire.framework */ = { 588 | isa = PBXReferenceProxy; 589 | fileType = wrapper.framework; 590 | path = Alamofire.framework; 591 | remoteRef = 721C79BB1C5D63A1008EFF59 /* PBXContainerItemProxy */; 592 | sourceTree = BUILT_PRODUCTS_DIR; 593 | }; 594 | 721C79BE1C5D63A1008EFF59 /* Alamofire tvOS Tests.xctest */ = { 595 | isa = PBXReferenceProxy; 596 | fileType = wrapper.cfbundle; 597 | path = "Alamofire tvOS Tests.xctest"; 598 | remoteRef = 721C79BD1C5D63A1008EFF59 /* PBXContainerItemProxy */; 599 | sourceTree = BUILT_PRODUCTS_DIR; 600 | }; 601 | 721C79C01C5D63A1008EFF59 /* Alamofire.framework */ = { 602 | isa = PBXReferenceProxy; 603 | fileType = wrapper.framework; 604 | path = Alamofire.framework; 605 | remoteRef = 721C79BF1C5D63A1008EFF59 /* PBXContainerItemProxy */; 606 | sourceTree = BUILT_PRODUCTS_DIR; 607 | }; 608 | 7252DD811C5E126D00E681E1 /* KVOController.framework */ = { 609 | isa = PBXReferenceProxy; 610 | fileType = wrapper.framework; 611 | path = KVOController.framework; 612 | remoteRef = 7252DD801C5E126D00E681E1 /* PBXContainerItemProxy */; 613 | sourceTree = BUILT_PRODUCTS_DIR; 614 | }; 615 | 7252DD831C5E126D00E681E1 /* KVOController.framework */ = { 616 | isa = PBXReferenceProxy; 617 | fileType = wrapper.framework; 618 | path = KVOController.framework; 619 | remoteRef = 7252DD821C5E126D00E681E1 /* PBXContainerItemProxy */; 620 | sourceTree = BUILT_PRODUCTS_DIR; 621 | }; 622 | /* End PBXReferenceProxy section */ 623 | 624 | /* Begin PBXResourcesBuildPhase section */ 625 | 721C792A1C5D418C008EFF59 /* Resources */ = { 626 | isa = PBXResourcesBuildPhase; 627 | buildActionMask = 2147483647; 628 | files = ( 629 | 721C79F81C5D8404008EFF59 /* LoginWindowController.xib in Resources */, 630 | 721C79401C5D41D1008EFF59 /* Assets.xcassets in Resources */, 631 | 721C79441C5D41DD008EFF59 /* MainMenu.xib in Resources */, 632 | ); 633 | runOnlyForDeploymentPostprocessing = 0; 634 | }; 635 | 721C795D1C5D426C008EFF59 /* Resources */ = { 636 | isa = PBXResourcesBuildPhase; 637 | buildActionMask = 2147483647; 638 | files = ( 639 | 721C79FC1C5D843E008EFF59 /* LoginViewController.xib in Resources */, 640 | 721C797D1C5D4294008EFF59 /* LaunchScreen.storyboard in Resources */, 641 | 7252DD7A1C5E0F5A00E681E1 /* ViewController.xib in Resources */, 642 | 721C79761C5D428E008EFF59 /* Assets.xcassets in Resources */, 643 | ); 644 | runOnlyForDeploymentPostprocessing = 0; 645 | }; 646 | 721C79821C5D42DE008EFF59 /* Resources */ = { 647 | isa = PBXResourcesBuildPhase; 648 | buildActionMask = 2147483647; 649 | files = ( 650 | ); 651 | runOnlyForDeploymentPostprocessing = 0; 652 | }; 653 | 721C79941C5D4301008EFF59 /* Resources */ = { 654 | isa = PBXResourcesBuildPhase; 655 | buildActionMask = 2147483647; 656 | files = ( 657 | ); 658 | runOnlyForDeploymentPostprocessing = 0; 659 | }; 660 | /* End PBXResourcesBuildPhase section */ 661 | 662 | /* Begin PBXSourcesBuildPhase section */ 663 | 721C79281C5D418C008EFF59 /* Sources */ = { 664 | isa = PBXSourcesBuildPhase; 665 | buildActionMask = 2147483647; 666 | files = ( 667 | 721C793F1C5D41D1008EFF59 /* AppDelegate.swift in Sources */, 668 | 721C79F71C5D8404008EFF59 /* LoginWindowController.swift in Sources */, 669 | 7203D1C61C5DBA450011E3F0 /* StatusItemController.swift in Sources */, 670 | ); 671 | runOnlyForDeploymentPostprocessing = 0; 672 | }; 673 | 721C795B1C5D426C008EFF59 /* Sources */ = { 674 | isa = PBXSourcesBuildPhase; 675 | buildActionMask = 2147483647; 676 | files = ( 677 | 721C79FB1C5D843E008EFF59 /* LoginViewController.swift in Sources */, 678 | 721C79781C5D428E008EFF59 /* ViewController.swift in Sources */, 679 | 7252DD891C5E14AA00E681E1 /* PreviewViewController.swift in Sources */, 680 | 7203D1C01C5DA0CD0011E3F0 /* NSDataExtensions.swift in Sources */, 681 | 721C79FE1C5D89A5008EFF59 /* UIAlertControllerExtensions.swift in Sources */, 682 | 7203D1BE1C5D9F690011E3F0 /* APNSManager.swift in Sources */, 683 | 721C79751C5D428E008EFF59 /* AppDelegate.swift in Sources */, 684 | ); 685 | runOnlyForDeploymentPostprocessing = 0; 686 | }; 687 | 721C797F1C5D42DE008EFF59 /* Sources */ = { 688 | isa = PBXSourcesBuildPhase; 689 | buildActionMask = 2147483647; 690 | files = ( 691 | 7252DD691C5DF12C00E681E1 /* PushNotification.swift in Sources */, 692 | 721C79CC1C5D642F008EFF59 /* User.swift in Sources */, 693 | 7252DD711C5DFA8B00E681E1 /* IncomingFileTransfer.swift in Sources */, 694 | 721C79A61C5D62A3008EFF59 /* Client.swift in Sources */, 695 | 721C79D51C5D7276008EFF59 /* CreatedUser.swift in Sources */, 696 | 7203D1C31C5DAD580011E3F0 /* ConnectionManager.swift in Sources */, 697 | 721C79CF1C5D64F2008EFF59 /* JSONDeserializable.swift in Sources */, 698 | 721C79DC1C5D78B7008EFF59 /* CredentialStorage.swift in Sources */, 699 | 721C79D21C5D6514008EFF59 /* AccessToken.swift in Sources */, 700 | 721C79F31C5D7969008EFF59 /* Keychain.swift in Sources */, 701 | 7252DD741C5DFC5D00E681E1 /* FileTransferContext.swift in Sources */, 702 | 7203D1CA1C5DE20F0011E3F0 /* DeviceName.swift in Sources */, 703 | 7252DD771C5DFD4400E681E1 /* OutgoingFileTransfer.swift in Sources */, 704 | 721C7A001C5D9505008EFF59 /* RegisteredDevice.swift in Sources */, 705 | ); 706 | runOnlyForDeploymentPostprocessing = 0; 707 | }; 708 | 721C79911C5D4301008EFF59 /* Sources */ = { 709 | isa = PBXSourcesBuildPhase; 710 | buildActionMask = 2147483647; 711 | files = ( 712 | 7252DD6A1C5DF12C00E681E1 /* PushNotification.swift in Sources */, 713 | 721C79CD1C5D642F008EFF59 /* User.swift in Sources */, 714 | 7252DD721C5DFA8B00E681E1 /* IncomingFileTransfer.swift in Sources */, 715 | 721C79A71C5D62A3008EFF59 /* Client.swift in Sources */, 716 | 721C79D61C5D7276008EFF59 /* CreatedUser.swift in Sources */, 717 | 7203D1C41C5DAD580011E3F0 /* ConnectionManager.swift in Sources */, 718 | 721C79D01C5D64F2008EFF59 /* JSONDeserializable.swift in Sources */, 719 | 721C79DD1C5D78B7008EFF59 /* CredentialStorage.swift in Sources */, 720 | 721C79D31C5D6514008EFF59 /* AccessToken.swift in Sources */, 721 | 721C79F41C5D7969008EFF59 /* Keychain.swift in Sources */, 722 | 7252DD751C5DFC5D00E681E1 /* FileTransferContext.swift in Sources */, 723 | 7203D1CB1C5DE20F0011E3F0 /* DeviceName.swift in Sources */, 724 | 7252DD781C5DFD4400E681E1 /* OutgoingFileTransfer.swift in Sources */, 725 | 721C7A011C5D9505008EFF59 /* RegisteredDevice.swift in Sources */, 726 | ); 727 | runOnlyForDeploymentPostprocessing = 0; 728 | }; 729 | /* End PBXSourcesBuildPhase section */ 730 | 731 | /* Begin PBXTargetDependency section */ 732 | 721C798A1C5D42DE008EFF59 /* PBXTargetDependency */ = { 733 | isa = PBXTargetDependency; 734 | target = 721C79831C5D42DE008EFF59 /* AresKit-Mac */; 735 | targetProxy = 721C79891C5D42DE008EFF59 /* PBXContainerItemProxy */; 736 | }; 737 | 721C799C1C5D4301008EFF59 /* PBXTargetDependency */ = { 738 | isa = PBXTargetDependency; 739 | target = 721C79951C5D4301008EFF59 /* AresKit-iOS */; 740 | targetProxy = 721C799B1C5D4301008EFF59 /* PBXContainerItemProxy */; 741 | }; 742 | 721C79C61C5D63BF008EFF59 /* PBXTargetDependency */ = { 743 | isa = PBXTargetDependency; 744 | name = "Alamofire OSX"; 745 | targetProxy = 721C79C51C5D63BF008EFF59 /* PBXContainerItemProxy */; 746 | }; 747 | 721C79CA1C5D63C6008EFF59 /* PBXTargetDependency */ = { 748 | isa = PBXTargetDependency; 749 | name = "Alamofire iOS"; 750 | targetProxy = 721C79C91C5D63C6008EFF59 /* PBXContainerItemProxy */; 751 | }; 752 | 7252DD871C5E127A00E681E1 /* PBXTargetDependency */ = { 753 | isa = PBXTargetDependency; 754 | name = "KVOController-iOS"; 755 | targetProxy = 7252DD861C5E127A00E681E1 /* PBXContainerItemProxy */; 756 | }; 757 | /* End PBXTargetDependency section */ 758 | 759 | /* Begin PBXVariantGroup section */ 760 | 721C79421C5D41DD008EFF59 /* MainMenu.xib */ = { 761 | isa = PBXVariantGroup; 762 | children = ( 763 | 721C79431C5D41DD008EFF59 /* Base */, 764 | ); 765 | name = MainMenu.xib; 766 | sourceTree = ""; 767 | }; 768 | 721C79791C5D4294008EFF59 /* LaunchScreen.storyboard */ = { 769 | isa = PBXVariantGroup; 770 | children = ( 771 | 721C797A1C5D4294008EFF59 /* Base */, 772 | ); 773 | name = LaunchScreen.storyboard; 774 | sourceTree = ""; 775 | }; 776 | /* End PBXVariantGroup section */ 777 | 778 | /* Begin XCBuildConfiguration section */ 779 | 721C79371C5D418C008EFF59 /* Debug */ = { 780 | isa = XCBuildConfiguration; 781 | buildSettings = { 782 | ALWAYS_SEARCH_USER_PATHS = NO; 783 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 784 | CLANG_CXX_LIBRARY = "libc++"; 785 | CLANG_ENABLE_MODULES = YES; 786 | CLANG_ENABLE_OBJC_ARC = YES; 787 | CLANG_WARN_BOOL_CONVERSION = YES; 788 | CLANG_WARN_CONSTANT_CONVERSION = YES; 789 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 790 | CLANG_WARN_EMPTY_BODY = YES; 791 | CLANG_WARN_ENUM_CONVERSION = YES; 792 | CLANG_WARN_INT_CONVERSION = YES; 793 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 794 | CLANG_WARN_UNREACHABLE_CODE = YES; 795 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 796 | CODE_SIGN_IDENTITY = "-"; 797 | COPY_PHASE_STRIP = NO; 798 | DEBUG_INFORMATION_FORMAT = dwarf; 799 | ENABLE_STRICT_OBJC_MSGSEND = YES; 800 | ENABLE_TESTABILITY = YES; 801 | GCC_C_LANGUAGE_STANDARD = gnu99; 802 | GCC_DYNAMIC_NO_PIC = NO; 803 | GCC_NO_COMMON_BLOCKS = YES; 804 | GCC_OPTIMIZATION_LEVEL = 0; 805 | GCC_PREPROCESSOR_DEFINITIONS = ( 806 | "DEBUG=1", 807 | "$(inherited)", 808 | ); 809 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 810 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 811 | GCC_WARN_UNDECLARED_SELECTOR = YES; 812 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 813 | GCC_WARN_UNUSED_FUNCTION = YES; 814 | GCC_WARN_UNUSED_VARIABLE = YES; 815 | MACOSX_DEPLOYMENT_TARGET = 10.11; 816 | MTL_ENABLE_DEBUG_INFO = YES; 817 | ONLY_ACTIVE_ARCH = YES; 818 | SDKROOT = macosx; 819 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 820 | }; 821 | name = Debug; 822 | }; 823 | 721C79381C5D418C008EFF59 /* Release */ = { 824 | isa = XCBuildConfiguration; 825 | buildSettings = { 826 | ALWAYS_SEARCH_USER_PATHS = NO; 827 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 828 | CLANG_CXX_LIBRARY = "libc++"; 829 | CLANG_ENABLE_MODULES = YES; 830 | CLANG_ENABLE_OBJC_ARC = YES; 831 | CLANG_WARN_BOOL_CONVERSION = YES; 832 | CLANG_WARN_CONSTANT_CONVERSION = YES; 833 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 834 | CLANG_WARN_EMPTY_BODY = YES; 835 | CLANG_WARN_ENUM_CONVERSION = YES; 836 | CLANG_WARN_INT_CONVERSION = YES; 837 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 838 | CLANG_WARN_UNREACHABLE_CODE = YES; 839 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 840 | CODE_SIGN_IDENTITY = "-"; 841 | COPY_PHASE_STRIP = NO; 842 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 843 | ENABLE_NS_ASSERTIONS = NO; 844 | ENABLE_STRICT_OBJC_MSGSEND = YES; 845 | GCC_C_LANGUAGE_STANDARD = gnu99; 846 | GCC_NO_COMMON_BLOCKS = YES; 847 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 848 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 849 | GCC_WARN_UNDECLARED_SELECTOR = YES; 850 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 851 | GCC_WARN_UNUSED_FUNCTION = YES; 852 | GCC_WARN_UNUSED_VARIABLE = YES; 853 | MACOSX_DEPLOYMENT_TARGET = 10.11; 854 | MTL_ENABLE_DEBUG_INFO = NO; 855 | SDKROOT = macosx; 856 | }; 857 | name = Release; 858 | }; 859 | 721C793A1C5D418C008EFF59 /* Debug */ = { 860 | isa = XCBuildConfiguration; 861 | buildSettings = { 862 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 863 | CLANG_ENABLE_MODULES = YES; 864 | CODE_SIGN_IDENTITY = "Developer ID Application"; 865 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; 866 | COMBINE_HIDPI_IMAGES = YES; 867 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 868 | INFOPLIST_FILE = Mac/Info.plist; 869 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 870 | PRODUCT_BUNDLE_IDENTIFIER = "com.indragie.ares-mac"; 871 | PRODUCT_NAME = Ares; 872 | PROVISIONING_PROFILE = ""; 873 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 874 | }; 875 | name = Debug; 876 | }; 877 | 721C793B1C5D418C008EFF59 /* Release */ = { 878 | isa = XCBuildConfiguration; 879 | buildSettings = { 880 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 881 | CLANG_ENABLE_MODULES = YES; 882 | CODE_SIGN_IDENTITY = "Developer ID Application"; 883 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; 884 | COMBINE_HIDPI_IMAGES = YES; 885 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 886 | INFOPLIST_FILE = Mac/Info.plist; 887 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 888 | PRODUCT_BUNDLE_IDENTIFIER = "com.indragie.ares-mac"; 889 | PRODUCT_NAME = Ares; 890 | PROVISIONING_PROFILE = ""; 891 | }; 892 | name = Release; 893 | }; 894 | 721C796F1C5D426C008EFF59 /* Debug */ = { 895 | isa = XCBuildConfiguration; 896 | buildSettings = { 897 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 898 | CLANG_ENABLE_MODULES = YES; 899 | CODE_SIGN_ENTITLEMENTS = Ares.entitlements; 900 | CODE_SIGN_IDENTITY = "iPhone Developer"; 901 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 902 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 903 | INFOPLIST_FILE = iOS/Info.plist; 904 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 905 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 906 | PRODUCT_BUNDLE_IDENTIFIER = com.indragie.ares; 907 | PRODUCT_NAME = Ares; 908 | SDKROOT = iphoneos; 909 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 910 | }; 911 | name = Debug; 912 | }; 913 | 721C79701C5D426C008EFF59 /* Release */ = { 914 | isa = XCBuildConfiguration; 915 | buildSettings = { 916 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 917 | CLANG_ENABLE_MODULES = YES; 918 | CODE_SIGN_ENTITLEMENTS = Ares.entitlements; 919 | CODE_SIGN_IDENTITY = "iPhone Developer"; 920 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 921 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 922 | INFOPLIST_FILE = iOS/Info.plist; 923 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 924 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 925 | PRODUCT_BUNDLE_IDENTIFIER = com.indragie.ares; 926 | PRODUCT_NAME = Ares; 927 | SDKROOT = iphoneos; 928 | VALIDATE_PRODUCT = YES; 929 | }; 930 | name = Release; 931 | }; 932 | 721C798E1C5D42DE008EFF59 /* Debug */ = { 933 | isa = XCBuildConfiguration; 934 | buildSettings = { 935 | CLANG_ENABLE_MODULES = YES; 936 | COMBINE_HIDPI_IMAGES = YES; 937 | CURRENT_PROJECT_VERSION = 1; 938 | DEFINES_MODULE = YES; 939 | DYLIB_COMPATIBILITY_VERSION = 1; 940 | DYLIB_CURRENT_VERSION = 1; 941 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 942 | FRAMEWORK_VERSION = A; 943 | INFOPLIST_FILE = AresKit/Info.plist; 944 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 945 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 946 | PRODUCT_BUNDLE_IDENTIFIER = "com.indragie.AresKit-Mac"; 947 | PRODUCT_NAME = AresKit; 948 | SKIP_INSTALL = YES; 949 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 950 | VERSIONING_SYSTEM = "apple-generic"; 951 | VERSION_INFO_PREFIX = ""; 952 | }; 953 | name = Debug; 954 | }; 955 | 721C798F1C5D42DE008EFF59 /* Release */ = { 956 | isa = XCBuildConfiguration; 957 | buildSettings = { 958 | CLANG_ENABLE_MODULES = YES; 959 | COMBINE_HIDPI_IMAGES = YES; 960 | CURRENT_PROJECT_VERSION = 1; 961 | DEFINES_MODULE = YES; 962 | DYLIB_COMPATIBILITY_VERSION = 1; 963 | DYLIB_CURRENT_VERSION = 1; 964 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 965 | FRAMEWORK_VERSION = A; 966 | INFOPLIST_FILE = AresKit/Info.plist; 967 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 968 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 969 | PRODUCT_BUNDLE_IDENTIFIER = "com.indragie.AresKit-Mac"; 970 | PRODUCT_NAME = AresKit; 971 | SKIP_INSTALL = YES; 972 | VERSIONING_SYSTEM = "apple-generic"; 973 | VERSION_INFO_PREFIX = ""; 974 | }; 975 | name = Release; 976 | }; 977 | 721C79A01C5D4302008EFF59 /* Debug */ = { 978 | isa = XCBuildConfiguration; 979 | buildSettings = { 980 | CLANG_ENABLE_MODULES = YES; 981 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 982 | CURRENT_PROJECT_VERSION = 1; 983 | DEFINES_MODULE = YES; 984 | DYLIB_COMPATIBILITY_VERSION = 1; 985 | DYLIB_CURRENT_VERSION = 1; 986 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 987 | INFOPLIST_FILE = AresKit/Info.plist; 988 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 989 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 990 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 991 | PRODUCT_BUNDLE_IDENTIFIER = "com.indragie.AresKit-iOS"; 992 | PRODUCT_NAME = AresKit; 993 | SDKROOT = iphoneos; 994 | SKIP_INSTALL = YES; 995 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 996 | TARGETED_DEVICE_FAMILY = "1,2"; 997 | VERSIONING_SYSTEM = "apple-generic"; 998 | VERSION_INFO_PREFIX = ""; 999 | }; 1000 | name = Debug; 1001 | }; 1002 | 721C79A11C5D4302008EFF59 /* Release */ = { 1003 | isa = XCBuildConfiguration; 1004 | buildSettings = { 1005 | CLANG_ENABLE_MODULES = YES; 1006 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 1007 | CURRENT_PROJECT_VERSION = 1; 1008 | DEFINES_MODULE = YES; 1009 | DYLIB_COMPATIBILITY_VERSION = 1; 1010 | DYLIB_CURRENT_VERSION = 1; 1011 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 1012 | INFOPLIST_FILE = AresKit/Info.plist; 1013 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 1014 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 1015 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1016 | PRODUCT_BUNDLE_IDENTIFIER = "com.indragie.AresKit-iOS"; 1017 | PRODUCT_NAME = AresKit; 1018 | SDKROOT = iphoneos; 1019 | SKIP_INSTALL = YES; 1020 | TARGETED_DEVICE_FAMILY = "1,2"; 1021 | VALIDATE_PRODUCT = YES; 1022 | VERSIONING_SYSTEM = "apple-generic"; 1023 | VERSION_INFO_PREFIX = ""; 1024 | }; 1025 | name = Release; 1026 | }; 1027 | /* End XCBuildConfiguration section */ 1028 | 1029 | /* Begin XCConfigurationList section */ 1030 | 721C79271C5D418C008EFF59 /* Build configuration list for PBXProject "Ares" */ = { 1031 | isa = XCConfigurationList; 1032 | buildConfigurations = ( 1033 | 721C79371C5D418C008EFF59 /* Debug */, 1034 | 721C79381C5D418C008EFF59 /* Release */, 1035 | ); 1036 | defaultConfigurationIsVisible = 0; 1037 | defaultConfigurationName = Release; 1038 | }; 1039 | 721C79391C5D418C008EFF59 /* Build configuration list for PBXNativeTarget "Ares-Mac" */ = { 1040 | isa = XCConfigurationList; 1041 | buildConfigurations = ( 1042 | 721C793A1C5D418C008EFF59 /* Debug */, 1043 | 721C793B1C5D418C008EFF59 /* Release */, 1044 | ); 1045 | defaultConfigurationIsVisible = 0; 1046 | defaultConfigurationName = Release; 1047 | }; 1048 | 721C796E1C5D426C008EFF59 /* Build configuration list for PBXNativeTarget "Ares-iOS" */ = { 1049 | isa = XCConfigurationList; 1050 | buildConfigurations = ( 1051 | 721C796F1C5D426C008EFF59 /* Debug */, 1052 | 721C79701C5D426C008EFF59 /* Release */, 1053 | ); 1054 | defaultConfigurationIsVisible = 0; 1055 | defaultConfigurationName = Release; 1056 | }; 1057 | 721C798D1C5D42DE008EFF59 /* Build configuration list for PBXNativeTarget "AresKit-Mac" */ = { 1058 | isa = XCConfigurationList; 1059 | buildConfigurations = ( 1060 | 721C798E1C5D42DE008EFF59 /* Debug */, 1061 | 721C798F1C5D42DE008EFF59 /* Release */, 1062 | ); 1063 | defaultConfigurationIsVisible = 0; 1064 | defaultConfigurationName = Release; 1065 | }; 1066 | 721C799F1C5D4302008EFF59 /* Build configuration list for PBXNativeTarget "AresKit-iOS" */ = { 1067 | isa = XCConfigurationList; 1068 | buildConfigurations = ( 1069 | 721C79A01C5D4302008EFF59 /* Debug */, 1070 | 721C79A11C5D4302008EFF59 /* Release */, 1071 | ); 1072 | defaultConfigurationIsVisible = 0; 1073 | defaultConfigurationName = Release; 1074 | }; 1075 | /* End XCConfigurationList section */ 1076 | }; 1077 | rootObject = 721C79241C5D418C008EFF59 /* Project object */; 1078 | } 1079 | -------------------------------------------------------------------------------- /client/Ares.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /client/AresKit/AccessToken.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccessToken.swift 3 | // Ares 4 | // 5 | // Created by Indragie on 1/30/16. 6 | // Copyright © 2016 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | public struct AccessToken: CustomStringConvertible, JSONDeserializable { 10 | public let username: String 11 | public let token: String 12 | 13 | internal init(username: String, token: String) { 14 | self.username = username 15 | self.token = token 16 | } 17 | 18 | // MARK: CustomStringConvertible 19 | 20 | public var description: String { 21 | return "AccessToken{username=\(username), token=\(token)}" 22 | } 23 | 24 | // MARK: JSONDeserializable 25 | 26 | public init?(JSON: JSONDictionary) { 27 | if let username = JSON["username"] as? String, 28 | token = JSON["token"] as? String { 29 | self.username = username 30 | self.token = token 31 | } else { 32 | self.username = "" 33 | self.token = "" 34 | return nil 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /client/AresKit/AresKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // AresKit.h 3 | // AresKit 4 | // 5 | // Created by Indragie on 1/30/16. 6 | // Copyright © 2016 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for AresKit. 12 | FOUNDATION_EXPORT double AresKitVersionNumber; 13 | 14 | //! Project version string for AresKit. 15 | FOUNDATION_EXPORT const unsigned char AresKitVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /client/AresKit/Client.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Client.swift 3 | // Ares 4 | // 5 | // Created by Indragie on 1/30/16. 6 | // Copyright © 2016 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | 12 | private let UserDefaultsDeviceUUIDKey = "deviceUUID"; 13 | private let DefaultAPIURL = NSURL(string: "http://yourappname.heroku.com")! 14 | 15 | public final class Client { 16 | public static let ErrorDomain = "AresKitClientErrorDomain" 17 | public static let APIErrorKey = "api_error" 18 | public enum ErrorCode: Int { 19 | case APIError 20 | case InvalidJSONResponse 21 | } 22 | 23 | private let manager: Manager 24 | private let URL: NSURL 25 | 26 | public init(URL: NSURL = DefaultAPIURL, configuration: NSURLSessionConfiguration = .defaultSessionConfiguration()) { 27 | self.URL = URL 28 | self.manager = Manager(configuration: configuration) 29 | } 30 | 31 | // MARK: API 32 | 33 | public func register(user: User, completionHandler: Result -> Void) { 34 | let request = Request( 35 | method: .POST, 36 | path: "/register", 37 | parameters: [ 38 | "username": user.username, 39 | "password": user.password 40 | ] 41 | ) 42 | requestModel(request, completionHandler: completionHandler) 43 | } 44 | 45 | public func authenticate(user: User, completionHandler: Result -> Void) { 46 | let request = Request( 47 | method: .POST, 48 | path: "/authenticate", 49 | parameters: [ 50 | "username": user.username, 51 | "password": user.password 52 | ] 53 | ) 54 | requestModel(request, completionHandler: completionHandler) 55 | } 56 | 57 | public func registerDevice(accessToken: AccessToken, pushToken: String? = nil, completionHandler: Result -> Void) { 58 | var parameters = [ 59 | "uuid": deviceUUID, 60 | "device_name": getDeviceName(), 61 | "token": accessToken.token 62 | ] 63 | if let pushToken = pushToken { 64 | parameters["push_token"] = pushToken 65 | } 66 | let request = Request( 67 | method: .POST, 68 | path: "/register_device", 69 | parameters: parameters 70 | ) 71 | requestModel(request, completionHandler: completionHandler) 72 | } 73 | 74 | public func getDevices(accessToken: AccessToken, completionHandler: Result<[RegisteredDevice], NSError> -> Void) { 75 | let request = Request(method: .GET, path: "/devices", parameters: [ 76 | "token": accessToken.token 77 | ]) 78 | requestModelArray(request, completionHandler: completionHandler) 79 | } 80 | 81 | public func send(accessToken: AccessToken, filePath: String, device: RegisteredDevice, completionHandler: Result -> Void) { 82 | let request = Request(method: .POST, path: "/send", parameters: [ 83 | "token": accessToken.token, 84 | "file_path": filePath, 85 | "to_id": device.uuid, 86 | "from_id": deviceUUID 87 | ]) 88 | requestVoid(request, completionHandler: completionHandler) 89 | } 90 | 91 | public var deviceUUID: String { 92 | let ud = NSUserDefaults.standardUserDefaults() 93 | let UUID: String 94 | if let storedUUID = ud.stringForKey(UserDefaultsDeviceUUIDKey) { 95 | UUID = storedUUID 96 | } else { 97 | UUID = NSUUID().UUIDString 98 | ud.setObject(UUID, forKey: UserDefaultsDeviceUUIDKey) 99 | } 100 | return UUID 101 | } 102 | 103 | // MARK: Primitives 104 | 105 | private struct Request { 106 | let method: Alamofire.Method 107 | let path: String 108 | let parameters: [String: AnyObject] 109 | } 110 | 111 | private static let InvalidJSONResponseError = NSError(domain: ErrorDomain, code: ErrorCode.InvalidJSONResponse.rawValue, userInfo: nil) 112 | 113 | private func requestModel(request: Request, completionHandler: Result -> Void) { 114 | requestJSON(request) { result in 115 | switch result { 116 | case let .Success(json): 117 | if let json = json as? JSONDictionary, model = T(JSON: json) { 118 | completionHandler(.Success(model)) 119 | } else { 120 | completionHandler(.Failure(self.dynamicType.InvalidJSONResponseError)) 121 | } 122 | case let .Failure(error): 123 | completionHandler(.Failure(error)) 124 | } 125 | } 126 | } 127 | 128 | private func requestModelArray(request: Request, completionHandler: Result<[T], NSError> -> Void) { 129 | requestJSON(request) { result in 130 | switch result { 131 | case let .Success(json): 132 | if let jsonArray = json as? [JSONDictionary] { 133 | var models = [T]() 134 | for deviceDict in jsonArray { 135 | if let model = T(JSON: deviceDict) { 136 | models.append(model) 137 | } else { 138 | completionHandler(.Failure(self.dynamicType.InvalidJSONResponseError)) 139 | return 140 | } 141 | } 142 | completionHandler(.Success(models)) 143 | } else { 144 | completionHandler(.Failure(self.dynamicType.InvalidJSONResponseError)) 145 | } 146 | case let .Failure(error): 147 | completionHandler(.Failure(error)) 148 | } 149 | } 150 | } 151 | 152 | private func requestJSON(request: Request, completionHandler: Result -> Void) { 153 | guard let components = NSURLComponents(URL: URL, resolvingAgainstBaseURL: false) else { 154 | fatalError("Invalid API base URL: \(URL)") 155 | } 156 | components.path = request.path 157 | guard let requestURL = components.URL else { 158 | fatalError("Unable to construct request URL") 159 | } 160 | manager.request(request.method, requestURL, parameters: request.parameters, encoding: .URL) 161 | .responseJSON { response in 162 | switch response.result { 163 | case let .Success(responseObject): 164 | if let json = responseObject as? JSONDictionary { 165 | if let success = json["success"] as? Bool, 166 | result = json["result"] where success { 167 | completionHandler(.Success(result)) 168 | } else { 169 | let error = self.dynamicType.constructAPIErrorFromJSON(json) 170 | completionHandler(.Failure(error)) 171 | } 172 | } else { 173 | completionHandler(.Failure(self.dynamicType.InvalidJSONResponseError)) 174 | } 175 | case let .Failure(error): 176 | completionHandler(.Failure(error)) 177 | } 178 | } 179 | } 180 | 181 | private func requestVoid(request: Request, completionHandler: Result -> Void) { 182 | requestJSON(request) { result in 183 | switch result { 184 | case .Success: 185 | completionHandler(.Success(())) 186 | case let .Failure(error): 187 | completionHandler(.Failure(error)) 188 | } 189 | } 190 | } 191 | 192 | private static func constructAPIErrorFromJSON(json: JSONDictionary) -> NSError { 193 | var userInfo = [String: AnyObject]() 194 | if let error = json["error"] as? String { 195 | userInfo[APIErrorKey] = error 196 | if let description = localizedDescriptionForAPIError(error) { 197 | userInfo[NSLocalizedDescriptionKey] = description 198 | } 199 | } 200 | return NSError(domain: ErrorDomain, code: ErrorCode.APIError.rawValue, userInfo: userInfo) 201 | } 202 | } 203 | 204 | private func localizedDescriptionForAPIError(error: String) -> String? { 205 | switch error { 206 | case "USER_EXISTS": 207 | return "A user with the specified username already exists."; 208 | case "USER_DOES_NOT_EXIST": 209 | return "A user with the specified username does not exist."; 210 | case "PASSWORD_INCORRECT": 211 | return "The specified password is incorrect."; 212 | case "INVALID_TOKEN": 213 | return "The specified access token is invalid."; 214 | default: return nil 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /client/AresKit/ConnectionManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConnectionManager.swift 3 | // Ares 4 | // 5 | // Created by Indragie on 1/30/16. 6 | // Copyright © 2016 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MultipeerConnectivity 11 | 12 | public protocol ConnectionManagerDelegate: AnyObject { 13 | func connectionManager(manager: ConnectionManager, didUpdateDevices devices: [Device]) 14 | func connectionManager(manager: ConnectionManager, didFailWithError error: NSError) 15 | func connectionManager(manager: ConnectionManager, willBeginIncomingFileTransfer transfer: IncomingFileTransfer) 16 | func connectionManager(manager: ConnectionManager, willBeginOutgoingFileTransfer transfer: OutgoingFileTransfer) 17 | } 18 | 19 | public struct Device: CustomStringConvertible { 20 | public enum Availability { 21 | case None 22 | case Local 23 | case Remote 24 | } 25 | 26 | public var description: String { 27 | return "Device{registeredDevice=\(registeredDevice), availability=\(availability)}" 28 | } 29 | 30 | public let registeredDevice: RegisteredDevice 31 | public let availability: Availability 32 | internal let peerID: MCPeerID? 33 | 34 | internal init(registeredDevice: RegisteredDevice, availability: Availability, peerID: MCPeerID? = nil) { 35 | self.registeredDevice = registeredDevice 36 | self.availability = availability 37 | self.peerID = peerID 38 | } 39 | } 40 | 41 | private let ServiceType = "ares-ft"; 42 | private let DiscoveryUUIDKey = "uuid"; 43 | 44 | @objc public final class ConnectionManager: NSObject, MCNearbyServiceAdvertiserDelegate, MCNearbyServiceBrowserDelegate, IncomingFileTransferDelegate, OutgoingFileTransferDelegate { 45 | private let client: Client 46 | private let token: AccessToken 47 | private let localPeer: MCPeerID 48 | private let advertiser: MCNearbyServiceAdvertiser 49 | private let browser: MCNearbyServiceBrowser 50 | private var peerIDToUUIDMap = [MCPeerID: String]() 51 | private var UUIDToPeerIDMap = [String: MCPeerID]() 52 | private var UUIDToNotificationMap = [String: [PushNotification]]() 53 | private var activeTransfers = [AnyObject]() 54 | private var isMonitoring: Bool = false 55 | 56 | private(set) public var devices = [Device]() { 57 | didSet { 58 | delegate?.connectionManager(self, didUpdateDevices: devices) 59 | } 60 | } 61 | 62 | public weak var delegate: ConnectionManagerDelegate? 63 | public weak var incomingFileTransferDelegate: IncomingFileTransferDelegate? 64 | public weak var outgoingFileTransferDelegate: OutgoingFileTransferDelegate? 65 | 66 | public init(client: Client, token: AccessToken) { 67 | self.client = client 68 | self.token = token 69 | 70 | localPeer = MCPeerID(displayName: getDeviceName()) 71 | let discoveryInfo = [DiscoveryUUIDKey: client.deviceUUID] 72 | advertiser = MCNearbyServiceAdvertiser(peer: localPeer, discoveryInfo: discoveryInfo, serviceType: ServiceType) 73 | browser = MCNearbyServiceBrowser(peer: localPeer, serviceType: ServiceType) 74 | 75 | super.init() 76 | 77 | advertiser.delegate = self 78 | browser.delegate = self 79 | 80 | #if os(iOS) 81 | let nc = NSNotificationCenter.defaultCenter() 82 | nc.addObserver(self, selector: "appWillResignActive:", name: UIApplicationWillResignActiveNotification, object: nil) 83 | nc.addObserver(self, selector: "appDidBecomeActive:", name: UIApplicationDidBecomeActiveNotification, object: nil) 84 | #endif 85 | } 86 | 87 | deinit { 88 | #if os(iOS) 89 | NSNotificationCenter.defaultCenter().removeObserver(self) 90 | #endif 91 | } 92 | 93 | public func getDeviceList(completionHandler: Void -> Void) { 94 | client.getDevices(token) { result in 95 | switch result { 96 | case let .Success(registeredDevices): 97 | self.devices = registeredDevices 98 | .filter { 99 | return $0.uuid != self.client.deviceUUID 100 | }.map { 101 | Device(registeredDevice: $0, availability: .None) 102 | } 103 | completionHandler() 104 | case let .Failure(error): 105 | self.delegate?.connectionManager(self, didFailWithError: error) 106 | } 107 | } 108 | } 109 | 110 | public func startMonitoring() { 111 | advertiser.startAdvertisingPeer() 112 | browser.startBrowsingForPeers() 113 | isMonitoring = true 114 | } 115 | 116 | public func stopMonitoring() { 117 | advertiser.stopAdvertisingPeer() 118 | browser.stopBrowsingForPeers() 119 | isMonitoring = false 120 | } 121 | 122 | public func queueNotification(notification: PushNotification) { 123 | if let peerID = UUIDToPeerIDMap[notification.deviceUUID] { 124 | requestTransferFromPeer(peerID, filePath: notification.filePath) 125 | } else { 126 | var notifications = UUIDToNotificationMap[notification.deviceUUID] ?? [] 127 | notifications.append(notification) 128 | UUIDToNotificationMap[notification.deviceUUID] = notifications 129 | } 130 | } 131 | 132 | // MARK: Notifications 133 | 134 | #if os(iOS) 135 | @objc private func appWillResignActive(notification: NSNotification) { 136 | advertiser.stopAdvertisingPeer() 137 | browser.stopBrowsingForPeers() 138 | peerIDToUUIDMap.removeAll() 139 | UUIDToPeerIDMap.removeAll() 140 | } 141 | 142 | @objc private func appDidBecomeActive(notification: NSNotification) { 143 | guard isMonitoring else { return } 144 | advertiser.startAdvertisingPeer() 145 | browser.startBrowsingForPeers() 146 | } 147 | #endif 148 | 149 | // MARK: Transfers 150 | 151 | func requestTransferFromPeer(peerID: MCPeerID, filePath: String) { 152 | let context = FileTransferContext(filePath: filePath) 153 | let transfer = IncomingFileTransfer(context: context, localPeerID: localPeer, remotePeerID: peerID) 154 | transfer.delegate = self 155 | activeTransfers.append(transfer) 156 | 157 | delegate?.connectionManager(self, willBeginIncomingFileTransfer: transfer) 158 | 159 | browser.invitePeer(peerID, toSession: transfer.session, withContext: context.archive(), timeout: 30) 160 | } 161 | 162 | // MARK: MCNearbyServiceAdvertiserDelegate 163 | 164 | public func advertiser(advertiser: MCNearbyServiceAdvertiser, didNotStartAdvertisingPeer error: NSError) { 165 | delegate?.connectionManager(self, didFailWithError: error) 166 | } 167 | 168 | public func advertiser(advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: NSData?, invitationHandler: (Bool, MCSession) -> Void) { 169 | guard let context = context.flatMap(FileTransferContext.init) else { return } 170 | 171 | let transfer = OutgoingFileTransfer(context: context, localPeerID: localPeer, remotePeerID: peerID) 172 | transfer.delegate = self 173 | activeTransfers.append(transfer) 174 | delegate?.connectionManager(self, willBeginOutgoingFileTransfer: transfer) 175 | 176 | invitationHandler(true, transfer.session) 177 | } 178 | 179 | // MARK: MCNearbyServiceBrowserDelegate 180 | 181 | public func browser(browser: MCNearbyServiceBrowser, didNotStartBrowsingForPeers error: NSError) { 182 | delegate?.connectionManager(self, didFailWithError: error) 183 | } 184 | 185 | public func browser(browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String : String]?) { 186 | guard let uuid = info?[DiscoveryUUIDKey] else { return } 187 | guard let (index, device) = findDeviceWithUUID(uuid) else { return } 188 | 189 | peerIDToUUIDMap[peerID] = uuid 190 | UUIDToPeerIDMap[uuid] = peerID 191 | 192 | let newDevice = Device(registeredDevice: device.registeredDevice, availability: .Local, peerID: peerID) 193 | devices[index] = newDevice 194 | 195 | if let notifications = UUIDToNotificationMap[uuid] { 196 | for notification in notifications { 197 | requestTransferFromPeer(peerID, filePath: notification.filePath) 198 | } 199 | UUIDToNotificationMap.removeValueForKey(uuid) 200 | } 201 | } 202 | 203 | public func browser(browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) { 204 | guard let uuid = peerIDToUUIDMap[peerID] else { return } 205 | guard let (index, device) = findDeviceWithUUID(uuid) else { return } 206 | 207 | peerIDToUUIDMap.removeValueForKey(peerID) 208 | UUIDToPeerIDMap.removeValueForKey(uuid) 209 | 210 | let newDevice = Device(registeredDevice: device.registeredDevice, availability: .None) 211 | devices[index] = newDevice 212 | } 213 | 214 | private func findDeviceWithUUID(uuid: String) -> (Int, Device)? { 215 | for (index, device) in devices.enumerate() { 216 | if device.registeredDevice.uuid == uuid { 217 | return (index, device) 218 | } 219 | } 220 | return nil 221 | } 222 | 223 | private func removeActiveTransfer(transfer: AnyObject) { 224 | if let index = activeTransfers.indexOf({ $0 === transfer }) { 225 | activeTransfers.removeAtIndex(index) 226 | } 227 | } 228 | 229 | // MARK: IncomingFileTransferDelegate 230 | 231 | public func incomingFileTransfer(transfer: IncomingFileTransfer, didReceiveFileWithName name: String, URL: NSURL) { 232 | removeActiveTransfer(transfer) 233 | incomingFileTransferDelegate?.incomingFileTransfer(transfer, didReceiveFileWithName: name, URL: URL) 234 | } 235 | 236 | public func incomingFileTransfer(transfer: IncomingFileTransfer, didFailToReceiveFileWithName name: String, error: NSError) { 237 | removeActiveTransfer(transfer) 238 | incomingFileTransferDelegate?.incomingFileTransfer(transfer, didFailToReceiveFileWithName: name, error: error) 239 | } 240 | 241 | public func incomingFileTransfer(transfer: IncomingFileTransfer, didStartReceivingFileWithName name: String, progress: NSProgress) { 242 | incomingFileTransferDelegate?.incomingFileTransfer(transfer, didStartReceivingFileWithName: name, progress: progress) 243 | } 244 | 245 | // MARK: OutgoingFileTransferDelegate 246 | 247 | public func outgoingFileTransferDidComplete(transfer: OutgoingFileTransfer) { 248 | removeActiveTransfer(transfer) 249 | outgoingFileTransferDelegate?.outgoingFileTransferDidComplete(transfer) 250 | } 251 | 252 | public func outgoingFileTransfer(transfer: OutgoingFileTransfer, didFailWithError error: NSError) { 253 | removeActiveTransfer(transfer) 254 | outgoingFileTransferDelegate?.outgoingFileTransfer(transfer, didFailWithError: error) 255 | } 256 | 257 | public func outgoingFileTransfer(transfer: OutgoingFileTransfer, didStartWithProgress progress: NSProgress) { 258 | outgoingFileTransferDelegate?.outgoingFileTransfer(transfer, didStartWithProgress: progress) 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /client/AresKit/CreatedUser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CreatedUser.swift 3 | // Ares 4 | // 5 | // Created by Indragie on 1/30/16. 6 | // Copyright © 2016 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | public struct CreatedUser: CustomStringConvertible, JSONDeserializable { 10 | public let username: String 11 | 12 | // MARK: CustomStringConvertible 13 | 14 | public var description: String { 15 | return "CreatedUser{username=\(username)}" 16 | } 17 | 18 | // MARK: JSONDeserializable 19 | 20 | public init?(JSON: JSONDictionary) { 21 | if let username = JSON["username"] as? String { 22 | self.username = username 23 | } else { 24 | self.username = "" 25 | return nil 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /client/AresKit/CredentialStorage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CredentialStorage.swift 3 | // Ares 4 | // 5 | // Created by Indragie on 1/30/16. 6 | // Copyright © 2016 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | private let ServiceName = "com.indragie.ares" 12 | private let UserDefaultsUsernameKey = "username" 13 | 14 | public final class CredentialStorage { 15 | public static let sharedInstance = CredentialStorage() 16 | private let keychain: Keychain 17 | 18 | private init() { 19 | keychain = Keychain(service: ServiceName) 20 | } 21 | 22 | private var _activeToken: AccessToken? 23 | public var activeToken: AccessToken? { 24 | get { 25 | if let accessToken = _activeToken { 26 | return accessToken 27 | } 28 | let ud = NSUserDefaults.standardUserDefaults() 29 | if let username = ud.stringForKey(UserDefaultsUsernameKey), 30 | token = keychain[username] { 31 | let accessToken = AccessToken(username: username, token: token) 32 | _activeToken = accessToken 33 | return accessToken 34 | } else { 35 | return nil 36 | } 37 | } 38 | set { 39 | let ud = NSUserDefaults.standardUserDefaults() 40 | if let accessToken = newValue { 41 | ud.setObject(accessToken.username, forKey: UserDefaultsUsernameKey) 42 | keychain[accessToken.username] = accessToken.token 43 | _activeToken = accessToken 44 | } else { 45 | ud.removeObjectForKey(UserDefaultsUsernameKey) 46 | if let previousToken = _activeToken { 47 | do { 48 | try keychain.remove(previousToken.username) 49 | } catch let error { 50 | assertionFailure("Error removing credentials from keychain: \(error)") 51 | } 52 | } 53 | _activeToken = nil 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /client/AresKit/DeviceName.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeviceName.swift 3 | // Ares 4 | // 5 | // Created by Indragie on 1/30/16. 6 | // Copyright © 2016 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | import UIKit 11 | #elseif os(OSX) 12 | import Cocoa 13 | #endif 14 | 15 | func getDeviceName() -> String { 16 | #if os(iOS) 17 | return UIDevice.currentDevice().name 18 | #elseif os(OSX) 19 | return NSHost.currentHost().localizedName ?? "Computer With No Name" 20 | #endif 21 | } 22 | -------------------------------------------------------------------------------- /client/AresKit/FileTransferContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileTransferContext.swift 3 | // Ares 4 | // 5 | // Created by Indragie on 1/31/16. 6 | // Copyright © 2016 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | private let ArchivedFilePathKey = "filePath"; 12 | 13 | public struct FileTransferContext { 14 | public let filePath: String 15 | 16 | init(filePath: String) { 17 | self.filePath = filePath 18 | } 19 | 20 | init?(data: NSData) { 21 | if let dict = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? [String: AnyObject], 22 | filePath = dict[ArchivedFilePathKey] as? String { 23 | self.filePath = filePath 24 | } else { 25 | return nil 26 | } 27 | } 28 | 29 | func archive() -> NSData { 30 | let dict: NSDictionary = [ArchivedFilePathKey: filePath] 31 | return NSKeyedArchiver.archivedDataWithRootObject(dict) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /client/AresKit/IncomingFileTransfer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IncomingFileTransfer.swift 3 | // Ares 4 | // 5 | // Created by Indragie on 1/31/16. 6 | // Copyright © 2016 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MultipeerConnectivity 11 | 12 | public protocol IncomingFileTransferDelegate: AnyObject { 13 | func incomingFileTransfer(transfer: IncomingFileTransfer, didStartReceivingFileWithName name: String, progress: NSProgress) 14 | func incomingFileTransfer(transfer: IncomingFileTransfer, didReceiveFileWithName name: String, URL: NSURL) 15 | func incomingFileTransfer(transfer: IncomingFileTransfer, didFailToReceiveFileWithName name: String, error: NSError) 16 | } 17 | 18 | @objc public final class IncomingFileTransfer: NSObject, MCSessionDelegate { 19 | public let context: FileTransferContext 20 | let session: MCSession 21 | private let remotePeerID: MCPeerID 22 | 23 | public weak var delegate: IncomingFileTransferDelegate? 24 | 25 | init(context: FileTransferContext, localPeerID: MCPeerID, remotePeerID: MCPeerID) { 26 | self.context = context 27 | self.session = MCSession(peer: localPeerID) 28 | self.remotePeerID = remotePeerID 29 | 30 | super.init() 31 | 32 | self.session.delegate = self 33 | } 34 | 35 | // MARK: MCSessionDelegate 36 | 37 | public func session(session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, withProgress progress: NSProgress) { 38 | guard peerID == remotePeerID else { return } 39 | 40 | delegate?.incomingFileTransfer(self, didStartReceivingFileWithName: resourceName, progress: progress) 41 | } 42 | 43 | public func session(session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, atURL localURL: NSURL, withError error: NSError?) { 44 | guard peerID == remotePeerID else { return } 45 | 46 | if let error = error { 47 | delegate?.incomingFileTransfer(self, didFailToReceiveFileWithName: resourceName, error: error) 48 | } else { 49 | delegate?.incomingFileTransfer(self, didReceiveFileWithName: resourceName, URL: localURL) 50 | } 51 | session.disconnect() 52 | } 53 | 54 | public func session(session: MCSession, didReceiveCertificate certificate: [AnyObject]?, fromPeer peerID: MCPeerID, certificateHandler: (Bool) -> Void) { 55 | guard peerID == remotePeerID else { return } 56 | certificateHandler(true) 57 | } 58 | 59 | // Unused 60 | 61 | public func session(session: MCSession, didReceiveData data: NSData, fromPeer peerID: MCPeerID) {} 62 | public func session(session: MCSession, peer peerID: MCPeerID, didChangeState state: MCSessionState) {} 63 | public func session(session: MCSession, didReceiveStream stream: NSInputStream, withName streamName: String, fromPeer peerID: MCPeerID) {} 64 | } 65 | -------------------------------------------------------------------------------- /client/AresKit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2016 Indragie Karunaratne. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /client/AresKit/JSONDeserializable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONDeserializable.swift 3 | // Ares 4 | // 5 | // Created by Indragie on 1/30/16. 6 | // Copyright © 2016 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | public typealias JSONDictionary = [String: AnyObject] 10 | 11 | public protocol JSONDeserializable { 12 | init?(JSON: JSONDictionary) 13 | } 14 | -------------------------------------------------------------------------------- /client/AresKit/OutgoingFileTransfer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OutgoingFileTransfer.swift 3 | // Ares 4 | // 5 | // Created by Indragie on 1/31/16. 6 | // Copyright © 2016 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MultipeerConnectivity 11 | 12 | public protocol OutgoingFileTransferDelegate: AnyObject { 13 | func outgoingFileTransfer(transfer: OutgoingFileTransfer, didStartWithProgress progress: NSProgress) 14 | func outgoingFileTransferDidComplete(transfer: OutgoingFileTransfer) 15 | func outgoingFileTransfer(transfer: OutgoingFileTransfer, didFailWithError error: NSError) 16 | } 17 | 18 | @objc public final class OutgoingFileTransfer: NSObject, MCSessionDelegate { 19 | public let context: FileTransferContext 20 | let session: MCSession 21 | private let remotePeerID: MCPeerID 22 | 23 | public weak var delegate: OutgoingFileTransferDelegate? 24 | 25 | init(context: FileTransferContext, localPeerID: MCPeerID, remotePeerID: MCPeerID) { 26 | self.context = context 27 | self.session = MCSession(peer: localPeerID) 28 | self.remotePeerID = remotePeerID 29 | 30 | super.init() 31 | 32 | self.session.delegate = self 33 | } 34 | 35 | // MARK: MCSessionDelegate 36 | 37 | public func session(session: MCSession, peer peerID: MCPeerID, didChangeState state: MCSessionState) { 38 | guard peerID == remotePeerID else { return } 39 | guard state == .Connected else { return } 40 | 41 | let URL = NSURL(fileURLWithPath: context.filePath) 42 | let name = (context.filePath as NSString).lastPathComponent 43 | let progress = session.sendResourceAtURL(URL, withName: name, toPeer: peerID) { error in 44 | if let error = error { 45 | self.delegate?.outgoingFileTransfer(self, didFailWithError: error) 46 | } else { 47 | self.delegate?.outgoingFileTransferDidComplete(self) 48 | } 49 | session.disconnect() 50 | } 51 | if let progress = progress { 52 | self.delegate?.outgoingFileTransfer(self, didStartWithProgress: progress) 53 | } 54 | } 55 | 56 | public func session(session: MCSession, didReceiveCertificate certificate: [AnyObject]?, fromPeer peerID: MCPeerID, certificateHandler: (Bool) -> Void) { 57 | guard peerID == remotePeerID else { return } 58 | certificateHandler(true) 59 | } 60 | 61 | // Unused 62 | 63 | public func session(session: MCSession, didReceiveData data: NSData, fromPeer peerID: MCPeerID) {} 64 | public func session(session: MCSession, didReceiveStream stream: NSInputStream, withName streamName: String, fromPeer peerID: MCPeerID) {} 65 | public func session(session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, withProgress progress: NSProgress) {} 66 | public func session(session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, atURL localURL: NSURL, withError error: NSError?) {} 67 | } 68 | -------------------------------------------------------------------------------- /client/AresKit/PushNotification.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PushNotification.swift 3 | // Ares 4 | // 5 | // Created by Indragie on 1/31/16. 6 | // Copyright © 2016 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | public struct PushNotification: CustomStringConvertible { 10 | public let deviceUUID: String 11 | public let filePath: String 12 | 13 | public init?(payload: [NSObject: AnyObject]) { 14 | if let deviceUUID = payload["device_id"] as? String, 15 | filePath = payload["path"] as? String { 16 | self.deviceUUID = deviceUUID 17 | self.filePath = filePath 18 | } else { 19 | self.deviceUUID = "" 20 | self.filePath = "" 21 | return nil 22 | } 23 | } 24 | 25 | // MARK: CustomStringConvertible 26 | 27 | public var description: String { 28 | return "PushNotification{deviceUUID=\(deviceUUID), filePath=\(filePath)}" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /client/AresKit/RegisteredDevice.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RegisteredDevice.swift 3 | // Ares 4 | // 5 | // Created by Indragie on 1/30/16. 6 | // Copyright © 2016 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | public struct RegisteredDevice: CustomStringConvertible, JSONDeserializable { 10 | public let uuid: String 11 | public let deviceName: String 12 | 13 | // MARK: CustomStringConvertible 14 | 15 | public var description: String { 16 | return "RegisteredDevice{uuid=\(uuid), deviceName=\(deviceName)}" 17 | } 18 | 19 | // MARK: JSONDeserializable 20 | 21 | public init?(JSON: JSONDictionary) { 22 | if let uuid = JSON["uuid"] as? String, 23 | deviceName = JSON["device_name"] as? String { 24 | self.uuid = uuid 25 | self.deviceName = deviceName 26 | } else { 27 | self.uuid = "" 28 | self.deviceName = "" 29 | return nil 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /client/AresKit/User.swift: -------------------------------------------------------------------------------- 1 | // 2 | // User.swift 3 | // Ares 4 | // 5 | // Created by Indragie on 1/30/16. 6 | // Copyright © 2016 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | public struct User: CustomStringConvertible { 10 | public let username: String 11 | public let password: String 12 | 13 | public init(username: String, password: String) { 14 | self.username = username 15 | self.password = password 16 | } 17 | 18 | // MARK: CustomStringConvertible 19 | 20 | public var description: String { 21 | return "User{username=\(username), password=\(password)}" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client/Mac/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Ares 4 | // 5 | // Created by Indragie on 1/30/16. 6 | // Copyright © 2016 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import AresKit 11 | 12 | @NSApplicationMain 13 | class AppDelegate: NSObject, NSApplicationDelegate, LoginWindowControllerDelegate { 14 | 15 | @IBOutlet weak var window: NSWindow! 16 | private var credentialStorage: CredentialStorage! 17 | private var client: Client! 18 | private var loginWindowController: LoginWindowController! 19 | private var statusItemController: StatusItemController! 20 | 21 | func applicationDidFinishLaunching(aNotification: NSNotification) { 22 | credentialStorage = CredentialStorage.sharedInstance 23 | client = Client() 24 | 25 | if let token = credentialStorage.activeToken { 26 | completeSetupWithToken(token) 27 | } else { 28 | showLoginWindowController() 29 | } 30 | } 31 | 32 | func showLoginWindowController() { 33 | loginWindowController = LoginWindowController(windowNibName: "LoginWindowController") 34 | loginWindowController.client = client 35 | loginWindowController.delegate = self 36 | loginWindowController.showWindow(nil) 37 | } 38 | 39 | func tearDownLoginWindowController() { 40 | loginWindowController.window?.orderOut(nil) 41 | loginWindowController = nil 42 | } 43 | 44 | func completeSetupWithToken(token: AccessToken) { 45 | statusItemController = StatusItemController(client: client, token: token) 46 | } 47 | 48 | // MARK: LoginWindowControllerDelegate 49 | 50 | func loginWindowController(controller: LoginWindowController, authenticatedWithToken token: AccessToken) { 51 | credentialStorage.activeToken = token 52 | tearDownLoginWindowController() 53 | completeSetupWithToken(token) 54 | } 55 | 56 | func loginWindowController(controller: LoginWindowController, failedToAuthenticateWithError error: NSError) { 57 | NSApp.presentError(error) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /client/Mac/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /client/Mac/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /client/Mac/Assets.xcassets/green_orb.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "green_orb.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "green_orb@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /client/Mac/Assets.xcassets/green_orb.imageset/green_orb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/Ares/bf09f2e124473a32b8e6dfc88d0b08dea0c48e8e/client/Mac/Assets.xcassets/green_orb.imageset/green_orb.png -------------------------------------------------------------------------------- /client/Mac/Assets.xcassets/green_orb.imageset/green_orb@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/Ares/bf09f2e124473a32b8e6dfc88d0b08dea0c48e8e/client/Mac/Assets.xcassets/green_orb.imageset/green_orb@2x.png -------------------------------------------------------------------------------- /client/Mac/Assets.xcassets/red_orb.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "red_orb.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "red_orb@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /client/Mac/Assets.xcassets/red_orb.imageset/red_orb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/Ares/bf09f2e124473a32b8e6dfc88d0b08dea0c48e8e/client/Mac/Assets.xcassets/red_orb.imageset/red_orb.png -------------------------------------------------------------------------------- /client/Mac/Assets.xcassets/red_orb.imageset/red_orb@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/Ares/bf09f2e124473a32b8e6dfc88d0b08dea0c48e8e/client/Mac/Assets.xcassets/red_orb.imageset/red_orb@2x.png -------------------------------------------------------------------------------- /client/Mac/Assets.xcassets/rocket.imageset/777.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/Ares/bf09f2e124473a32b8e6dfc88d0b08dea0c48e8e/client/Mac/Assets.xcassets/rocket.imageset/777.png -------------------------------------------------------------------------------- /client/Mac/Assets.xcassets/rocket.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "777.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /client/Mac/Assets.xcassets/yellow_orb.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "yellow_orb.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "yellow_orb@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /client/Mac/Assets.xcassets/yellow_orb.imageset/yellow_orb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/Ares/bf09f2e124473a32b8e6dfc88d0b08dea0c48e8e/client/Mac/Assets.xcassets/yellow_orb.imageset/yellow_orb.png -------------------------------------------------------------------------------- /client/Mac/Assets.xcassets/yellow_orb.imageset/yellow_orb@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/Ares/bf09f2e124473a32b8e6dfc88d0b08dea0c48e8e/client/Mac/Assets.xcassets/yellow_orb.imageset/yellow_orb@2x.png -------------------------------------------------------------------------------- /client/Mac/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | Default 536 | 537 | 538 | 539 | 540 | 541 | 542 | Left to Right 543 | 544 | 545 | 546 | 547 | 548 | 549 | Right to Left 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | Default 561 | 562 | 563 | 564 | 565 | 566 | 567 | Left to Right 568 | 569 | 570 | 571 | 572 | 573 | 574 | Right to Left 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | -------------------------------------------------------------------------------- /client/Mac/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2016 Indragie Karunaratne. All rights reserved. 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | NSAppTransportSecurity 34 | 35 | NSAllowsArbitraryLoads 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /client/Mac/LoginWindowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginWindowController.swift 3 | // Ares 4 | // 5 | // Created by Indragie on 1/30/16. 6 | // Copyright © 2016 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import AresKit 11 | 12 | protocol LoginWindowControllerDelegate: AnyObject { 13 | func loginWindowController(controller: LoginWindowController, failedToAuthenticateWithError error: NSError) 14 | func loginWindowController(controller: LoginWindowController, authenticatedWithToken token: AccessToken) 15 | } 16 | 17 | class LoginWindowController: NSWindowController { 18 | var client: Client? 19 | weak var delegate: LoginWindowControllerDelegate? 20 | 21 | @IBOutlet weak var usernameField: NSTextField! 22 | @IBOutlet weak var passwordField: NSSecureTextField! 23 | 24 | convenience init() { 25 | self.init(windowNibName: "LoginWindowController") 26 | } 27 | 28 | private func constructUser() -> User { 29 | return User(username: usernameField.stringValue, password: passwordField.stringValue) 30 | } 31 | 32 | @IBAction func login(sender: NSButton) { 33 | authenticate(constructUser()) 34 | } 35 | 36 | @IBAction func register(sender: NSButton) { 37 | guard let client = client else { return } 38 | let user = constructUser() 39 | client.register(user) { result in 40 | switch result { 41 | case .Success: 42 | self.authenticate(user) 43 | case let .Failure(error): 44 | self.delegate?.loginWindowController(self, failedToAuthenticateWithError: error) 45 | } 46 | } 47 | } 48 | 49 | private func authenticate(user: User) { 50 | guard let client = client else { return } 51 | client.authenticate(user) { result in 52 | switch result { 53 | case let .Success(token): 54 | self.registerDevice(token) 55 | case let .Failure(error): 56 | self.delegate?.loginWindowController(self, failedToAuthenticateWithError: error) 57 | } 58 | } 59 | } 60 | 61 | private func registerDevice(token: AccessToken) { 62 | guard let client = client else { return } 63 | client.registerDevice(token) { result in 64 | switch result { 65 | case .Success: 66 | self.delegate?.loginWindowController(self, authenticatedWithToken: token) 67 | case let .Failure(error): 68 | self.delegate?.loginWindowController(self, failedToAuthenticateWithError: error) 69 | } 70 | } 71 | } 72 | } 73 | 74 | -------------------------------------------------------------------------------- /client/Mac/LoginWindowController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | NSAllRomanInputSourcesLocaleIdentifier 53 | 54 | 55 | 56 | 69 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /client/Mac/StatusItemController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatusItemController.swift 3 | // Ares 4 | // 5 | // Created by Indragie on 1/30/16. 6 | // Copyright © 2016 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import AresKit 11 | 12 | @objc final class StatusItemController: NSObject, ConnectionManagerDelegate, OutgoingFileTransferDelegate, NSWindowDelegate { 13 | let statusItem: NSStatusItem 14 | private let client: Client 15 | private let token: AccessToken 16 | private let connectionManager: ConnectionManager 17 | 18 | init(client: Client, token: AccessToken) { 19 | self.client = client 20 | self.token = token 21 | 22 | statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(NSSquareStatusItemLength) 23 | connectionManager = ConnectionManager(client: client, token: token) 24 | 25 | super.init() 26 | 27 | connectionManager.delegate = self 28 | connectionManager.outgoingFileTransferDelegate = self 29 | connectionManager.getDeviceList { 30 | self.connectionManager.startMonitoring() 31 | } 32 | 33 | if let button = statusItem.button { 34 | button.title = "🚀" 35 | if let window = button.window { 36 | window.registerForDraggedTypes([NSFilenamesPboardType]) 37 | window.delegate = self 38 | } 39 | } 40 | } 41 | 42 | deinit { 43 | connectionManager.stopMonitoring() 44 | } 45 | 46 | // MARK: ConnectionManagerDelegate 47 | 48 | func connectionManager(manager: ConnectionManager, didUpdateDevices devices: [Device]) { 49 | let menu = NSMenu(title: "Devices") 50 | for device in devices { 51 | let registeredDevice = device.registeredDevice 52 | if registeredDevice.uuid == client.deviceUUID { 53 | continue 54 | } 55 | guard let item = menu.addItemWithTitle(registeredDevice.deviceName, action: "doNothing:", keyEquivalent: "") else { continue } 56 | item.target = self 57 | item.image = menuItemImageForDevice(device) 58 | } 59 | statusItem.menu = menu 60 | } 61 | 62 | @objc private func doNothing(sender: AnyObject) {} 63 | 64 | private func menuItemImageForDevice(device: Device) -> NSImage? { 65 | switch device.availability { 66 | case .Local: 67 | return NSImage(named: "green_orb") 68 | case .Remote: 69 | return NSImage(named: "yellow_orb") 70 | case .None: 71 | return NSImage(named: "red_orb") 72 | } 73 | } 74 | 75 | func connectionManager(manager: ConnectionManager, didFailWithError error: NSError) { 76 | print(error) 77 | } 78 | 79 | func connectionManager(manager: ConnectionManager, willBeginIncomingFileTransfer transfer: IncomingFileTransfer) {} 80 | 81 | func connectionManager(manager: ConnectionManager, willBeginOutgoingFileTransfer transfer: OutgoingFileTransfer) { 82 | print("Sending \(transfer.context.filePath)") 83 | } 84 | 85 | // MARK: OutgoingFileTransferDelegate 86 | 87 | func outgoingFileTransfer(transfer: OutgoingFileTransfer, didStartWithProgress progress: NSProgress) { 88 | let notification = NSUserNotification() 89 | notification.title = "File Transfer Started" 90 | notification.informativeText = "\(transfer.context.filePath)" 91 | NSUserNotificationCenter.defaultUserNotificationCenter().deliverNotification(notification) 92 | } 93 | 94 | func outgoingFileTransfer(transfer: OutgoingFileTransfer, didFailWithError error: NSError) { 95 | print("Sending \(transfer.context.filePath) failed: \(error)") 96 | } 97 | 98 | func outgoingFileTransferDidComplete(transfer: OutgoingFileTransfer) { 99 | let notification = NSUserNotification() 100 | notification.title = "File Transfer Complete" 101 | notification.informativeText = "\(transfer.context.filePath)" 102 | NSUserNotificationCenter.defaultUserNotificationCenter().deliverNotification(notification) 103 | } 104 | 105 | // MARK: Drag and Drop 106 | 107 | func draggingEntered(sender: NSDraggingInfo) -> NSDragOperation { 108 | return .Copy 109 | } 110 | 111 | func performDragOperation(sender: NSDraggingInfo) -> Bool { 112 | let pasteboard = sender.draggingPasteboard() 113 | guard let types = pasteboard.types else { return false } 114 | if types.contains(NSFilenamesPboardType) { 115 | if let files = pasteboard.propertyListForType(NSFilenamesPboardType) as? [String], 116 | device = connectionManager.devices.first?.registeredDevice { 117 | for file in files { 118 | client.send(token, filePath: file, device: device) { result in 119 | if let error = result.error { 120 | print(error) 121 | } 122 | } 123 | } 124 | return true 125 | } 126 | } 127 | return false 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /client/iOS/APNSManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APNSManager.swift 3 | // Ares 4 | // 5 | // Created by Indragie on 1/30/16. 6 | // Copyright © 2016 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class APNSManager { 12 | var token: String? 13 | init() {} 14 | } 15 | -------------------------------------------------------------------------------- /client/iOS/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Ares-iOS 4 | // 5 | // Created by Indragie on 1/30/16. 6 | // Copyright © 2016 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AresKit 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | private var apnsManager: APNSManager! 15 | private var viewController: ViewController! 16 | 17 | var window: UIWindow? 18 | 19 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 20 | let settings = UIUserNotificationSettings(forTypes: [.Alert], categories: nil) 21 | UIApplication.sharedApplication().registerUserNotificationSettings(settings) 22 | UIApplication.sharedApplication().registerForRemoteNotifications() 23 | 24 | window = UIWindow(frame: UIScreen.mainScreen().bounds) 25 | window?.backgroundColor = .whiteColor() 26 | 27 | apnsManager = APNSManager() 28 | let client = Client() 29 | let credentialStorage = CredentialStorage.sharedInstance 30 | viewController = ViewController(client: client, credentialStorage: credentialStorage, apnsManager: apnsManager) 31 | 32 | window?.rootViewController = UINavigationController(rootViewController: viewController) 33 | window?.makeKeyAndVisible() 34 | 35 | if let payload = launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey] as? [NSObject: AnyObject], 36 | notification = PushNotification(payload: payload) { 37 | viewController.handlePushNotification(notification) 38 | } 39 | 40 | return true 41 | } 42 | 43 | func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) { 44 | apnsManager.token = deviceToken.hexadecimalString 45 | } 46 | 47 | func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) { 48 | print(error) 49 | } 50 | 51 | func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) { 52 | guard let notification = PushNotification(payload: userInfo) else { return } 53 | viewController.handlePushNotification(notification) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /client/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /client/iOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /client/iOS/Assets.xcassets/placeholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "placeholder.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "placeholder@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "placeholder@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /client/iOS/Assets.xcassets/placeholder.imageset/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/Ares/bf09f2e124473a32b8e6dfc88d0b08dea0c48e8e/client/iOS/Assets.xcassets/placeholder.imageset/placeholder.png -------------------------------------------------------------------------------- /client/iOS/Assets.xcassets/placeholder.imageset/placeholder@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/Ares/bf09f2e124473a32b8e6dfc88d0b08dea0c48e8e/client/iOS/Assets.xcassets/placeholder.imageset/placeholder@2x.png -------------------------------------------------------------------------------- /client/iOS/Assets.xcassets/placeholder.imageset/placeholder@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/Ares/bf09f2e124473a32b8e6dfc88d0b08dea0c48e8e/client/iOS/Assets.xcassets/placeholder.imageset/placeholder@3x.png -------------------------------------------------------------------------------- /client/iOS/Assets.xcassets/rocket.imageset/777.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/Ares/bf09f2e124473a32b8e6dfc88d0b08dea0c48e8e/client/iOS/Assets.xcassets/rocket.imageset/777.png -------------------------------------------------------------------------------- /client/iOS/Assets.xcassets/rocket.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "777.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /client/iOS/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /client/iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | NSAppTransportSecurity 38 | 39 | NSAllowsArbitraryLoads 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /client/iOS/LoginViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginViewController.swift 3 | // Ares 4 | // 5 | // Created by Indragie on 1/30/16. 6 | // Copyright © 2016 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AresKit 11 | 12 | protocol LoginViewControllerDelegate: AnyObject { 13 | func loginViewController(controller: LoginViewController, authenticatedWithToken token: AccessToken) 14 | } 15 | 16 | class LoginViewController: UIViewController { 17 | private let apnsManager: APNSManager 18 | 19 | var client: Client? 20 | weak var delegate: LoginViewControllerDelegate? 21 | 22 | @IBOutlet weak var usernameField: UITextField! 23 | @IBOutlet weak var passwordField: UITextField! 24 | 25 | init(apnsManager: APNSManager) { 26 | self.apnsManager = apnsManager 27 | 28 | super.init(nibName: nil, bundle: nil) 29 | 30 | title = "Login" 31 | } 32 | 33 | required init?(coder aDecoder: NSCoder) { 34 | fatalError("init(coder:) has not been implemented") 35 | } 36 | 37 | private func constructUser() -> User { 38 | return User(username: usernameField.text ?? "", password: passwordField.text ?? "") 39 | } 40 | 41 | @IBAction func login(sender: UIButton) { 42 | authenticate(constructUser()) 43 | } 44 | 45 | @IBAction func register(sender: UIButton) { 46 | guard let client = client else { return } 47 | let user = constructUser() 48 | client.register(user) { result in 49 | switch result { 50 | case .Success: 51 | self.authenticate(user) 52 | case let .Failure(error): 53 | self.showAlertForError(error) 54 | } 55 | } 56 | } 57 | 58 | private func authenticate(user: User) { 59 | guard let client = client else { return } 60 | client.authenticate(user) { result in 61 | switch result { 62 | case let .Success(token): 63 | self.registerDevice(token) 64 | case let .Failure(error): 65 | self.showAlertForError(error) 66 | } 67 | } 68 | } 69 | 70 | private func registerDevice(token: AccessToken) { 71 | guard let client = client else { return } 72 | client.registerDevice(token, pushToken: apnsManager.token) { result in 73 | switch result { 74 | case .Success: 75 | self.delegate?.loginViewController(self, authenticatedWithToken: token) 76 | case let .Failure(error): 77 | self.showAlertForError(error) 78 | } 79 | } 80 | } 81 | 82 | private func showAlertForError(error: NSError) { 83 | let alertController = UIAlertController.alertControllerWithError(error) 84 | presentViewController(alertController, animated: true, completion: nil) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /client/iOS/LoginViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 60 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /client/iOS/NSDataExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSDataExtensions.swift 3 | // Ares 4 | // 5 | // Created by Indragie on 1/30/16. 6 | // Copyright © 2016 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension NSData { 12 | public var hexadecimalString: String { 13 | var bytes = [UInt8](count: length, repeatedValue: 0) 14 | getBytes(&bytes, length: length) 15 | 16 | let hexString = NSMutableString() 17 | for byte in bytes { 18 | hexString.appendFormat("%02x", UInt(byte)) 19 | } 20 | return hexString as String 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /client/iOS/PreviewViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewViewController.swift 3 | // Ares 4 | // 5 | // Created by Indragie on 1/31/16. 6 | // Copyright © 2016 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import QuickLook 11 | 12 | class PreviewViewController: QLPreviewController, QLPreviewControllerDataSource { 13 | @objc private class PreviewItem: NSObject, QLPreviewItem { 14 | @objc let previewItemTitle: String? 15 | @objc let previewItemURL: NSURL 16 | 17 | init(previewItemTitle: String?, previewItemURL: NSURL) { 18 | self.previewItemTitle = previewItemTitle 19 | self.previewItemURL = previewItemURL 20 | } 21 | } 22 | 23 | private let previewItem: PreviewItem 24 | 25 | init(fileName: String, URL: NSURL) { 26 | previewItem = PreviewItem(previewItemTitle: fileName, previewItemURL: URL) 27 | super.init(nibName: nil, bundle: nil) 28 | dataSource = self 29 | } 30 | 31 | required init?(coder aDecoder: NSCoder) { 32 | fatalError("init(coder:) has not been implemented") 33 | } 34 | 35 | // MARK: QLPreviewControllerDataSource 36 | 37 | func numberOfPreviewItemsInPreviewController(controller: QLPreviewController) -> Int { 38 | return 1 39 | } 40 | 41 | func previewController(controller: QLPreviewController, previewItemAtIndex index: Int) -> QLPreviewItem { 42 | return previewItem 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /client/iOS/UIAlertControllerExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIAlertControllerExtensions.swift 3 | // Ares 4 | // 5 | // Created by Indragie on 1/30/16. 6 | // Copyright © 2016 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIAlertController { 12 | static func alertControllerWithError(error: NSError) -> UIAlertController { 13 | let alert = UIAlertController(title: error.localizedDescription, message: error.localizedRecoverySuggestion, preferredStyle: .Alert) 14 | alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .Default, handler: nil)) 15 | return alert 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /client/iOS/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Ares-iOS 4 | // 5 | // Created by Indragie on 1/30/16. 6 | // Copyright © 2016 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AresKit 11 | import KVOController 12 | import QuickLook 13 | 14 | class ViewController: UIViewController, LoginViewControllerDelegate, ConnectionManagerDelegate, IncomingFileTransferDelegate, QLPreviewControllerDelegate { 15 | private enum State { 16 | case Default 17 | case WaitingForDiscovery 18 | case Transferring 19 | } 20 | 21 | private let credentialStorage: CredentialStorage 22 | private let apnsManager: APNSManager 23 | private let client: Client 24 | private var _KVOController: FBKVOController! 25 | 26 | private var connectionManager: ConnectionManager? 27 | private var queuedPushNotifications = [PushNotification]() 28 | private var temporaryFileURL: NSURL? 29 | 30 | private var state = State.Default { 31 | didSet { 32 | loadViewIfNeeded() 33 | switch state { 34 | case .Default: 35 | placeholderImageView.hidden = false 36 | determinateProgressStackView.hidden = true 37 | indeterminateProgressStackView.hidden = true 38 | activityIndicator.stopAnimating() 39 | case .WaitingForDiscovery: 40 | placeholderImageView.hidden = true 41 | determinateProgressStackView.hidden = true 42 | indeterminateProgressStackView.hidden = false 43 | activityIndicator.startAnimating() 44 | case .Transferring: 45 | placeholderImageView.hidden = true 46 | determinateProgressStackView.hidden = false 47 | indeterminateProgressStackView.hidden = true 48 | activityIndicator.stopAnimating() 49 | } 50 | } 51 | } 52 | 53 | @IBOutlet weak var progressView: UIProgressView! 54 | @IBOutlet weak var progressLabel: UILabel! 55 | @IBOutlet weak var activityIndicator: UIActivityIndicatorView! 56 | @IBOutlet weak var determinateProgressStackView: UIStackView! 57 | @IBOutlet weak var indeterminateProgressStackView: UIStackView! 58 | @IBOutlet weak var placeholderImageView: UIImageView! 59 | 60 | init(client: Client, credentialStorage: CredentialStorage, apnsManager: APNSManager) { 61 | self.client = client 62 | self.credentialStorage = credentialStorage 63 | self.apnsManager = apnsManager 64 | 65 | super.init(nibName: nil, bundle: nil) 66 | 67 | self._KVOController = FBKVOController(observer: self) 68 | title = "🚀 Ares" 69 | } 70 | 71 | required init?(coder aDecoder: NSCoder) { 72 | fatalError("init(coder:) has not been implemented") 73 | } 74 | 75 | override func viewDidLoad() { 76 | super.viewDidLoad() 77 | if let token = credentialStorage.activeToken { 78 | completeSetupWithToken(token) 79 | } else { 80 | dispatch_async(dispatch_get_main_queue()) { 81 | self.presentLoginViewController() 82 | } 83 | } 84 | } 85 | 86 | // MARK: Login 87 | 88 | private func presentLoginViewController() { 89 | let loginViewController = LoginViewController(apnsManager: apnsManager) 90 | loginViewController.client = client 91 | loginViewController.delegate = self 92 | 93 | let navigationController = UINavigationController(rootViewController: loginViewController) 94 | presentViewController(navigationController, animated: true, completion: nil) 95 | } 96 | 97 | private func completeSetupWithToken(token: AccessToken) { 98 | let connectionManager = ConnectionManager(client: client, token: token) 99 | connectionManager.delegate = self 100 | connectionManager.incomingFileTransferDelegate = self 101 | connectionManager.getDeviceList { 102 | connectionManager.startMonitoring() 103 | 104 | self.queuedPushNotifications.forEach(connectionManager.queueNotification) 105 | self.queuedPushNotifications.removeAll() 106 | } 107 | self.connectionManager = connectionManager 108 | } 109 | 110 | // MARK: Notification Handling 111 | 112 | func handlePushNotification(notification: PushNotification) { 113 | state = .WaitingForDiscovery 114 | if let connectionManager = connectionManager { 115 | connectionManager.queueNotification(notification) 116 | } else { 117 | queuedPushNotifications.append(notification) 118 | } 119 | } 120 | 121 | // MARK: LoginViewControllerDelegae 122 | 123 | func loginViewController(controller: LoginViewController, authenticatedWithToken token: AccessToken) { 124 | credentialStorage.activeToken = token 125 | completeSetupWithToken(token) 126 | 127 | dismissViewControllerAnimated(true, completion: nil) 128 | } 129 | 130 | // MARK: ConnectionManagerDelegate 131 | 132 | func connectionManager(manager: ConnectionManager, willBeginOutgoingFileTransfer transfer: OutgoingFileTransfer) {} 133 | 134 | func connectionManager(manager: ConnectionManager, willBeginIncomingFileTransfer transfer: IncomingFileTransfer) { 135 | print("Receiving \(transfer.context.filePath)") 136 | } 137 | 138 | func connectionManager(manager: ConnectionManager, didFailWithError error: NSError) { 139 | print(error) 140 | } 141 | 142 | func connectionManager(manager: ConnectionManager, didUpdateDevices devices: [Device]) {} 143 | 144 | // MARK: IncomingFileTransferDelegate 145 | 146 | func incomingFileTransfer(transfer: IncomingFileTransfer, didStartReceivingFileWithName name: String, progress: NSProgress) { 147 | let fileName = (transfer.context.filePath as NSString).lastPathComponent 148 | 149 | dispatch_async(dispatch_get_main_queue()) { 150 | self.progressLabel.text = "Receiving \(fileName)..." 151 | self.state = .Transferring 152 | } 153 | 154 | _KVOController.observe(progress, keyPath: "fractionCompleted", options: []) { (_, _, _) in 155 | dispatch_async(dispatch_get_main_queue()) { 156 | self.progressView.progress = Float(progress.fractionCompleted) 157 | } 158 | } 159 | } 160 | 161 | func incomingFileTransfer(transfer: IncomingFileTransfer, didFailToReceiveFileWithName name: String, error: NSError) { 162 | print("Failed to receive \(name): \(error)") 163 | } 164 | 165 | func incomingFileTransfer(transfer: IncomingFileTransfer, didReceiveFileWithName name: String, URL: NSURL) { 166 | if let _ = presentedViewController { 167 | dismissViewControllerAnimated(true) { 168 | self.showPreviewControllerForFileName(name, URL: URL) 169 | } 170 | } else { 171 | showPreviewControllerForFileName(name, URL: URL) 172 | } 173 | } 174 | 175 | private func showPreviewControllerForFileName(name: String, URL: NSURL) { 176 | guard let directoryURL = URL.URLByDeletingLastPathComponent else { return } 177 | let fixedURL = directoryURL.URLByAppendingPathComponent(name) 178 | temporaryFileURL = fixedURL 179 | 180 | let fm = NSFileManager.defaultManager() 181 | do { try fm.removeItemAtURL(fixedURL) } catch _ {} 182 | do { 183 | try fm.moveItemAtURL(URL, toURL: fixedURL) 184 | } catch let error { 185 | fatalError("Error moving \(URL) to \(fixedURL): \(error)") 186 | } 187 | 188 | dispatch_async(dispatch_get_main_queue()) { 189 | let previewController = PreviewViewController(fileName: name, URL: fixedURL) 190 | previewController.delegate = self 191 | self.presentViewController(previewController, animated: true) { 192 | self.state = .Default 193 | } 194 | } 195 | } 196 | 197 | // MARK: QLPreviewControllerDelegate 198 | 199 | func previewControllerDidDismiss(controller: QLPreviewController) { 200 | guard let fileURL = temporaryFileURL else { return } 201 | do { 202 | try NSFileManager.defaultManager().removeItemAtURL(fileURL) 203 | } catch _ {} 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /client/iOS/ViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 38 | 39 | 40 | 41 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | -------------------------------------------------------------------------------- /server/Procfile: -------------------------------------------------------------------------------- 1 | web: node index.js 2 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | var bodyParser = require('body-parser'); 4 | var MongoClient = require('mongodb').MongoClient; 5 | var assert = require('assert'); 6 | var async = require('async'); 7 | var bcrypt = require('bcrypt'); 8 | var jwt = require('jsonwebtoken'); 9 | var apn = require('apn'); 10 | var path = require('path'); 11 | 12 | // ######## CONSTANTS ######### 13 | 14 | USERS_COLLECTION = 'users'; 15 | DEVICES_COLLECTION = 'devices'; 16 | TOKEN_EXPIRY_SECONDS = 86400; // 24 hours 17 | ERROR_USER_EXISTS = new Error('USER_EXISTS'); 18 | ERROR_USER_DOES_NOT_EXIST = new Error('USER_DOES_NOT_EXIST'); 19 | ERROR_DEVICE_DOES_NOT_EXIST = new Error('DEVICE_DOES_NOT_EXIST'); 20 | ERROR_PASSWORD_INCORRECT = new Error('PASSWORD_INCORRECT'); 21 | ERROR_INVALID_TOKEN = new Error('INVALID_TOKEN'); 22 | 23 | // ######## EXPRESS ######### 24 | 25 | app.set('port', (process.env.PORT || 5000)); 26 | app.set('mongo_uri', process.env.MONGOLAB_URI); 27 | app.set('secret', process.env.APP_SECRET); 28 | 29 | app.use(bodyParser.urlencoded({ extended: false })); 30 | 31 | app.get('/', function (req, res) { 32 | res.send('Hello World!'); 33 | }); 34 | 35 | app.listen(app.get('port'), function () { 36 | console.log('Example server started'); 37 | }); 38 | 39 | app.post('/register', function(req, res, next) { 40 | async.waterfall([ 41 | connectMongoDB, 42 | async.apply(createUser, req.body.username, req.body.password) 43 | ], function(err, result) { 44 | if (err) { 45 | return next(err); 46 | } else { 47 | res.json({ 48 | success: true, 49 | result: { username: result.ops[0].username } 50 | }); 51 | } 52 | }); 53 | }); 54 | 55 | app.post('/authenticate', function(req, res, next) { 56 | var username = req.body.username; 57 | 58 | async.waterfall([ 59 | connectMongoDB, 60 | async.apply(getUser, username), 61 | function(user, callback) { 62 | if (user) { 63 | callback(null, user); 64 | } else { 65 | callback(ERROR_USER_DOES_NOT_EXIST); 66 | } 67 | }, 68 | function(user, callback) { 69 | verifyPassword(user.password_hash, req.body.password, function(err, res) { 70 | if (err) { 71 | callback(err); 72 | } else { 73 | callback(null, res, user); 74 | } 75 | }); 76 | }, 77 | function(verified, user, callback) { 78 | if (verified) { 79 | callback(null, user); 80 | } else { 81 | callback(ERROR_PASSWORD_INCORRECT); 82 | } 83 | }, 84 | ], function(err, user) { 85 | if (err) { 86 | return next(err); 87 | } else { 88 | var token = jwt.sign(user, app.get('secret'), { 89 | expiresIn: TOKEN_EXPIRY_SECONDS 90 | }); 91 | res.json({ 92 | success: true, 93 | result: { 94 | username: username, 95 | token: token 96 | } 97 | }); 98 | } 99 | }); 100 | }); 101 | 102 | app.use(function(req, res, next) { 103 | var token = req.body.token || req.query.token || req.headers['x-access-token']; 104 | if (token) { 105 | jwt.verify(token, app.get('secret'), function(err, user) { 106 | if (err) { 107 | next(ERROR_INVALID_TOKEN); 108 | } else { 109 | req.user = user; 110 | next(); 111 | } 112 | }); 113 | } else { 114 | res.status(403).json({ 115 | success: false, 116 | error: 'No authentication token provided' 117 | }); 118 | } 119 | }); 120 | 121 | app.post('/register_device', function(req, res, next) { 122 | var userID = req.user._id; 123 | var uuid = req.body.uuid; 124 | var deviceName = req.body.device_name; 125 | var pushToken = req.body.push_token; 126 | async.waterfall([ 127 | connectMongoDB, 128 | async.apply(registerDevice, userID, uuid, deviceName, pushToken) 129 | ], function(err, result) { 130 | if (err) { 131 | return next(err); 132 | } else { 133 | res.json({ 134 | success: true, 135 | result: { 136 | uuid: uuid, 137 | device_name: deviceName 138 | } 139 | }); 140 | } 141 | }); 142 | }); 143 | 144 | app.get('/devices', function(req, res, next) { 145 | async.waterfall([ 146 | connectMongoDB, 147 | async.apply(getDevices, req.user._id) 148 | ], function(err, result) { 149 | if (err) { 150 | return next(err); 151 | } else { 152 | res.json({ 153 | success: true, 154 | result: result 155 | }); 156 | } 157 | }); 158 | }); 159 | 160 | 161 | var apnConnection = new apn.Connection({ production: false }); 162 | 163 | app.post('/send', function(req, res, next) { 164 | async.waterfall([ 165 | connectMongoDB, 166 | async.apply(getDevice, req.user._id, req.body.to_id), 167 | function(device, callback) { 168 | if (device) { 169 | var apnsDevice = new apn.Device(device.push_token); 170 | var notification = new apn.Notification(); 171 | var filePath = req.body.file_path; 172 | notification.alert = path.basename(filePath); 173 | notification.payload = { 'device_id': req.body.from_id, 'path': filePath }; 174 | apnConnection.pushNotification(notification, apnsDevice); 175 | callback(null, {}); 176 | } else { 177 | callback(ERROR_DEVICE_DOES_NOT_EXIST); 178 | } 179 | } 180 | ], function(err, result) { 181 | if (err) { 182 | return next(err); 183 | } else { 184 | res.json({ 185 | success: true, 186 | result: result 187 | }); 188 | } 189 | }); 190 | }); 191 | 192 | app.use(function(err, req, res, next) { 193 | res.status(400).json({ 194 | success: false, 195 | error: err.message 196 | }); 197 | }); 198 | 199 | // ######## MONGODB ######### 200 | 201 | var connectMongoDB = function(callback) { 202 | MongoClient.connect(app.get('mongo_uri'), callback); 203 | }; 204 | 205 | var getUser = function(username, db, callback) { 206 | var users = db.collection(USERS_COLLECTION); 207 | users.findOne({ username: username }, callback); 208 | }; 209 | 210 | var createUser = function(username, password, db, finalCallback) { 211 | async.waterfall([ 212 | async.apply(getUser, username, db), 213 | function(user, callback) { 214 | if (user) { 215 | callback(ERROR_USER_EXISTS); 216 | } else { 217 | callback(null, password); 218 | } 219 | }, 220 | hashPassword, 221 | function(hash, callback) { 222 | db.collection(USERS_COLLECTION).insert({ 223 | username: username, 224 | password_hash: hash 225 | }, callback); 226 | } 227 | ], finalCallback); 228 | }; 229 | 230 | var registerDevice = function(userID, uuid, deviceName, pushToken, db, callback) { 231 | db.collection(DEVICES_COLLECTION).insert({ 232 | user_id: userID, 233 | _id: uuid, 234 | device_name: deviceName, 235 | push_token: pushToken 236 | }, callback); 237 | }; 238 | 239 | var getDevices = function(userID, db, callback) { 240 | var devices = db.collection(DEVICES_COLLECTION); 241 | devices.find({ user_id: userID }).map(function(device) { 242 | return { 243 | uuid: device._id, 244 | device_name: device.device_name 245 | }; 246 | }).toArray(callback); 247 | }; 248 | 249 | var getDevice = function(userID, deviceID, db, callback) { 250 | db.collection(DEVICES_COLLECTION).findOne({ 251 | user_id: userID, 252 | _id: deviceID 253 | }, callback); 254 | }; 255 | 256 | // ######## HASHING ######### 257 | 258 | var hashPassword = function(password, callback) { 259 | bcrypt.genSalt(10, function(err, salt) { 260 | bcrypt.hash(password, salt, callback); 261 | }); 262 | }; 263 | 264 | var verifyPassword = function(hash, password, callback) { 265 | bcrypt.compare(password, hash, callback); 266 | }; 267 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ares-server", 3 | "version": "1.0.0", 4 | "description": "Back-end for the Ares file transfer service", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/indragiek/Ares.git" 12 | }, 13 | "keywords": [ 14 | "ios", 15 | "mac", 16 | "file", 17 | "transfer", 18 | "bluetooth", 19 | "wifi" 20 | ], 21 | "author": "Indragie Karunaratne", 22 | "license": "UNLICENSED", 23 | "bugs": { 24 | "url": "https://github.com/indragiek/Ares/issues" 25 | }, 26 | "homepage": "https://github.com/indragiek/Ares#readme", 27 | "dependencies": { 28 | "apn": "^1.7.5", 29 | "async": "^1.5.2", 30 | "bcrypt": "^0.8.5", 31 | "body-parser": "^1.14.2", 32 | "express": "^4.13.4", 33 | "jsonwebtoken": "^5.5.4", 34 | "kerberos": "~0.0.17", 35 | "mongodb": "^2.1.4", 36 | "ws": "^1.0.1" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /steps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/Ares/bf09f2e124473a32b8e6dfc88d0b08dea0c48e8e/steps.png --------------------------------------------------------------------------------