Full Code of ethanal/Nimbus for AI

master 2259a68f7e4c cached
70 files
222.7 KB
54.4k tokens
57 symbols
1 requests
Download .txt
Showing preview only (242K chars total). Download the full file or copy to clipboard to get everything.
Repository: ethanal/Nimbus
Branch: master
Commit: 2259a68f7e4c
Files: 70
Total size: 222.7 KB

Directory structure:
gitextract_4bhtxnk0/

├── .gitignore
├── LICENSE
├── NimbusMenuBarApp/
│   └── Nimbus/
│       ├── Nimbus/
│       │   ├── APIClient.swift
│       │   ├── AppDelegate.swift
│       │   ├── Base.lproj/
│       │   │   └── MainMenu.xib
│       │   ├── Images.xcassets/
│       │   │   └── AppIcon.appiconset/
│       │   │       └── Contents.json
│       │   ├── Info.plist
│       │   ├── KeychainManager.swift
│       │   ├── Nimbus-Bridging-Header.h
│       │   ├── PreferencesManager.swift
│       │   ├── PreferencesWindowController.swift
│       │   ├── PreferencesWindowController.xib
│       │   ├── ScreenshotWatcher.h
│       │   ├── ScreenshotWatcher.m
│       │   ├── StatusItemView.swift
│       │   ├── SwiftyJSON.swift
│       │   └── main.swift
│       ├── Nimbus.xcodeproj/
│       │   ├── project.pbxproj
│       │   ├── project.xcworkspace/
│       │   │   └── contents.xcworkspacedata
│       │   └── xcshareddata/
│       │       └── xcschemes/
│       │           └── Nimbus.xcscheme
│       └── NimbusTests/
│           ├── Info.plist
│           └── NimbusTests.swift
├── README.md
├── api_docs.md
├── graphics_assets/
│   └── assets.sketch/
│       ├── Data
│       ├── metadata
│       └── version
├── manage.py
├── nimbus/
│   ├── __init__.py
│   ├── apps/
│   │   ├── __init__.py
│   │   ├── accounts/
│   │   │   ├── __init__.py
│   │   │   ├── forms.py
│   │   │   ├── urls.py
│   │   │   └── views.py
│   │   ├── api/
│   │   │   ├── __init__.py
│   │   │   ├── urls.py
│   │   │   └── views.py
│   │   └── media/
│   │       ├── __init__.py
│   │       ├── admin.py
│   │       ├── models.py
│   │       ├── serializers.py
│   │       ├── urls.py
│   │       └── views.py
│   ├── settings/
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── local.py
│   │   ├── production.py
│   │   └── secret.sample.py
│   ├── static/
│   │   ├── css/
│   │   │   ├── base.css
│   │   │   ├── dashboard.css
│   │   │   ├── login.css
│   │   │   └── share_preview.css
│   │   └── js/
│   │       ├── dashboard.js
│   │       └── jquery-photo-resize.js
│   ├── templates/
│   │   ├── admin/
│   │   │   └── base_site.html
│   │   ├── nimbus/
│   │   │   ├── accounts/
│   │   │   │   ├── dashboard.html
│   │   │   │   ├── login.html
│   │   │   │   └── media_table_row.html
│   │   │   ├── media/
│   │   │   │   ├── share_base.html
│   │   │   │   ├── share_download.html
│   │   │   │   ├── share_img_preview.html
│   │   │   │   └── share_txt_preview.html
│   │   │   └── page_base.html
│   │   └── rest_framework/
│   │       ├── api.html
│   │       └── login.html
│   ├── utils.py
│   └── wsgi.py
└── requirements/
    ├── base.txt
    ├── local.txt
    └── production.txt

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]

# C extensions
*.so

# Distribution / packaging
.Python
env/
bin/
build/
develop-eggs/
dist/
eggs/
#lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml

# Translations
*.mo

# Mr Developer
.mr.developer.cfg
.project
.pydevproject

# Rope
.ropeproject

# Django stuff:
*.log
*.pot

# Sphinx documentation
docs/_build/


# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate

# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
#
# Pods/


testing_database.db
secret.py
collected_static
newrelic.ini


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2014 Ethan Lowman

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

================================================
FILE: NimbusMenuBarApp/Nimbus/Nimbus/APIClient.swift
================================================

//
//  APIClient.swift
//  Nimbus
//
//  Created by Ethan Lowman on 7/25/14.
//  Copyright (c) 2014 Ethanal. All rights reserved.
//

import Cocoa

class APIClient: NSObject {
    var prefs = PreferencesManager()
    
    var apiRoot: String {
    get {
        return "https://api." + prefs.hostname
    }
    }
    
    var authToken: String
    
    override init() {
        if let token = KeychainService.loadToken() {
            authToken = token as String
        } else {
            authToken = ""
        }
        
        super.init()
    }
    
    
    func request(uri: NSString, withAuth: Bool) -> NSMutableURLRequest {
        let r = NSMutableURLRequest(URL: NSURL(string: apiRoot + (uri as String))!)
        if withAuth {
            r.setValue("Token \(authToken)", forHTTPHeaderField: "Authorization")
        }
        r.timeoutInterval = 10.0
        return r
    }
    
    
    func getTokenForUsername(username: NSString, password: NSString, successCallback: ((NSString!) -> Void)?, errorCallback: (() -> Void)?) {
        let req = request("/api-token-auth", withAuth: false)
        req.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
        req.HTTPMethod = "POST"
        
        let requestJSON = JSON([
            "username": username,
            "password": password
            ])
        
        req.HTTPBody = requestJSON.rawString()!.dataUsingEncoding(NSUTF8StringEncoding)
        NSURLConnection.sendAsynchronousRequest(req, queue: NSOperationQueue.mainQueue()) {(response, data, error) in
            guard let data = data where error == nil else {
                if (errorCallback != nil) {
                    errorCallback!()
                }
                return
            }
            
            let responseString = NSString(data: data, encoding: NSUTF8StringEncoding)
            var responseJSON = JSON(data: data)
            
            if let token = responseJSON["token"].string {
                if (successCallback != nil) {
                    self.authToken = token
                    successCallback!(token)

                }
            } else  {
                print(responseString)
                if (errorCallback != nil) {
                    errorCallback!()
                }
                return
            }
        
        }
    }
    
    
    func addFile(fileData: NSData, filename: NSString, successCallback: ((NSURL!) -> Void)?, errorCallback: (() -> Void)?) {
        let req = request("/media/add_file", withAuth: true)
        req.HTTPMethod = "POST"
        
        let boundary = "gc0p4Jq0M2Yt08jU534c0pgc0p4Jq0M2Yt08jU534c0p"
        let contentType = "multipart/form-data; boundary=\(boundary)"
        req.setValue(contentType, forHTTPHeaderField: "Content-Type")
        
        let postData = NSMutableData()
        postData.appendData("\r\n--\(boundary)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
        postData.appendData("Content-Disposition: form-data; name=\"file\"; filename=\"\(filename)\"\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
        postData.appendData("Content-Type: application/octet-stream\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
        postData.appendData(NSData(data: fileData))
        postData.appendData("\r\n--\(boundary)--\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
        
        req.HTTPBody = postData
        
        NSURLConnection.sendAsynchronousRequest(req, queue: NSOperationQueue.mainQueue()) {(response, data, error) in
            guard let data = data where error == nil else {
                if (errorCallback != nil) {
                    errorCallback!()
                }
                return
            }
            
            let responseString = NSString(data: data, encoding: NSUTF8StringEncoding)
            var responseJSON = JSON(data: data)
            
            if let shareURL = responseJSON["share_url"].URL {
                if (successCallback != nil) {
                    successCallback!(shareURL)
                }
            } else  {
                print(responseString)
                if (errorCallback != nil) {
                    errorCallback!()
                }
                return
            }

        }
    }
    
    func addLink(link: NSURL, successCallback: ((NSURL!) -> Void)?, errorCallback: (() -> Void)?) {
        let req = request("/media/add_link", withAuth: true)
        req.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
        req.HTTPMethod = "POST"
        
        let requestJSON = JSON([
            "target_url": link.absoluteString
            ])
        
        req.HTTPBody = requestJSON.rawString()!.dataUsingEncoding(NSUTF8StringEncoding)
        NSURLConnection.sendAsynchronousRequest(req, queue: NSOperationQueue.mainQueue()) {(response, data, error) in
            guard let data = data where error == nil else {
                if (errorCallback != nil) {
                    errorCallback!()
                }
                return
            }
            
            let responseString = NSString(data: data, encoding: NSUTF8StringEncoding)
            var responseJSON = JSON(data: data)
            
            if let shareURL = responseJSON["share_url"].URL {
                if (successCallback != nil) {
                    successCallback!(shareURL)
                }
            } else  {
                print(responseString)
                if (errorCallback != nil) {
                    errorCallback!()
                }
                return
            }

            
        }
    }

}


================================================
FILE: NimbusMenuBarApp/Nimbus/Nimbus/AppDelegate.swift
================================================
//
//  AppDelegate.swift
//  Nimbus
//
//  Created by Ethan Lowman on 7/25/14.
//  Copyright (c) 2014 Ethanal. All rights reserved.
//

import Cocoa

class AppDelegate: NSObject, NSApplicationDelegate, NSMetadataQueryDelegate {
    
    var statusView = StatusItemView()
    var prefs = PreferencesManager()
    var api = APIClient()
    var sw: ScreenshotWatcher?
    
    override init() {
        super.init()
    }
    
    func applicationDidFinishLaunching(aNotification: NSNotification) {
        sw = ScreenshotWatcher(uploadFileCallback: uploadFile);
        sw!.startWatchingPath(screenCaptureLocation())
    }
    
    func screenCaptureLocation() -> String {
        let screenCapturePrefs: NSDictionary? = NSUserDefaults.standardUserDefaults().persistentDomainForName("com.apple.screencapture")
        
        let location: String? = screenCapturePrefs?.valueForKey("location")?.stringByExpandingTildeInPath as String?
        
        if let loc = location {
            return loc.hasSuffix("/") ? loc : (loc + "/")
        }
        
        return ("~/Desktop" as NSString).stringByExpandingTildeInPath + "/"
    }
    
    func uploadFile(fileData: NSData!, filename: String!) {
        print(filename);
//        NSMetadataItem *metadata = NSMetadataItem(URL: filename)
//        BOOL isScreenshot = [[metadata valueForAttribute:@"kMDItemIsScreenCapture"] integerValue] == 1;
        statusView.status = .Working
        api.addFile(fileData, filename: filename, successCallback: {(shareURL: NSURL!) -> Void in
            let pb = NSPasteboard.generalPasteboard()
            pb.clearContents()
            pb.writeObjects([shareURL.absoluteString])
            self.statusView.status = .Success
            }, errorCallback: {() -> Void in
                print("Error uploading file")
                self.statusView.status = .Error
            })
    }
    
    func uploadLink(link: NSURL) {
        statusView.status = .Working
        api.addLink(link, successCallback: {(shareURL: NSURL!) -> Void in
            let pb = NSPasteboard.generalPasteboard()
            pb.clearContents()
            pb.writeObjects([shareURL.absoluteString])
            self.statusView.status = .Success
            }, errorCallback: {() -> Void in
                print("Error uploading link")
                self.statusView.status = .Error
            })
        
    }
}


================================================
FILE: NimbusMenuBarApp/Nimbus/Nimbus/Base.lproj/MainMenu.xib
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6254" systemVersion="14C109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
    <dependencies>
        <deployment identifier="macosx"/>
        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6254"/>
    </dependencies>
    <objects>
        <customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
            <connections>
                <outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
            </connections>
        </customObject>
        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
        <customObject id="-3" userLabel="Application" customClass="NSObject"/>
        <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Nimbus" customModuleProvider="target"/>
        <menu title="Main Menu" systemMenu="main" id="TcC-0d-qzF">
            <items>
                <menuItem title="Nimbus" id="j6L-po-JZS">
                    <modifierMask key="keyEquivalentModifierMask"/>
                    <menu key="submenu" title="Nimbus" systemMenu="apple" id="18D-nT-g35">
                        <items>
                            <menuItem title="About Nimbus" id="ie0-vZ-qQE">
                                <modifierMask key="keyEquivalentModifierMask"/>
                                <connections>
                                    <action selector="orderFrontStandardAboutPanel:" target="-1" id="OLT-T6-eIh"/>
                                </connections>
                            </menuItem>
                            <menuItem isSeparatorItem="YES" id="Lgi-uc-Ggn"/>
                            <menuItem title="Preferences…" keyEquivalent="," id="wbL-nE-kQl"/>
                            <menuItem isSeparatorItem="YES" id="23F-jQ-kQG"/>
                            <menuItem title="Services" id="IcQ-XN-bs3">
                                <modifierMask key="keyEquivalentModifierMask"/>
                                <menu key="submenu" title="Services" systemMenu="services" id="CP7-iw-wGz"/>
                            </menuItem>
                            <menuItem isSeparatorItem="YES" id="nZr-cS-3Tp"/>
                            <menuItem title="Hide Nimbus" keyEquivalent="h" id="rPB-nS-l2J">
                                <connections>
                                    <action selector="hide:" target="-1" id="TUP-iv-CoE"/>
                                </connections>
                            </menuItem>
                            <menuItem title="Hide Others" keyEquivalent="h" id="nH8-9K-WRu">
                                <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
                                <connections>
                                    <action selector="hideOtherApplications:" target="-1" id="5Vt-hm-Low"/>
                                </connections>
                            </menuItem>
                            <menuItem title="Show All" id="oIv-Yx-hIT">
                                <modifierMask key="keyEquivalentModifierMask"/>
                                <connections>
                                    <action selector="unhideAllApplications:" target="-1" id="63A-lT-6ua"/>
                                </connections>
                            </menuItem>
                            <menuItem isSeparatorItem="YES" id="pk8-av-w9O"/>
                            <menuItem title="Quit Nimbus" keyEquivalent="q" id="yCv-S3-Q4L">
                                <connections>
                                    <action selector="terminate:" target="-1" id="o86-pe-rOt"/>
                                </connections>
                            </menuItem>
                        </items>
                    </menu>
                </menuItem>
                <menuItem title="File" id="baT-qG-pra">
                    <modifierMask key="keyEquivalentModifierMask"/>
                    <menu key="submenu" title="File" id="weq-jU-nIX">
                        <items>
                            <menuItem title="New" keyEquivalent="n" id="5DU-gI-c3T">
                                <connections>
                                    <action selector="newDocument:" target="-1" id="N50-bd-Cgm"/>
                                </connections>
                            </menuItem>
                            <menuItem title="Open…" keyEquivalent="o" id="wtK-vV-wP1">
                                <connections>
                                    <action selector="openDocument:" target="-1" id="Ovf-SJ-Svn"/>
                                </connections>
                            </menuItem>
                            <menuItem title="Open Recent" id="ISk-fF-uwZ">
                                <modifierMask key="keyEquivalentModifierMask"/>
                                <menu key="submenu" title="Open Recent" systemMenu="recentDocuments" id="qan-vU-Fh4">
                                    <items>
                                        <menuItem title="Clear Menu" id="yPE-fU-NZb">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <connections>
                                                <action selector="clearRecentDocuments:" target="-1" id="aaW-Jh-K6L"/>
                                            </connections>
                                        </menuItem>
                                    </items>
                                </menu>
                            </menuItem>
                            <menuItem isSeparatorItem="YES" id="R9j-gD-LXw"/>
                            <menuItem title="Close" keyEquivalent="w" id="JBQ-fE-1zH">
                                <connections>
                                    <action selector="performClose:" target="-1" id="hk9-H8-tLD"/>
                                </connections>
                            </menuItem>
                            <menuItem title="Save…" keyEquivalent="s" id="dEM-te-Zmm">
                                <connections>
                                    <action selector="saveDocument:" target="-1" id="DbK-WV-kFw"/>
                                </connections>
                            </menuItem>
                            <menuItem title="Save As…" keyEquivalent="S" id="cA0-DT-V0B">
                                <connections>
                                    <action selector="saveDocumentAs:" target="-1" id="G4H-U3-QgM"/>
                                </connections>
                            </menuItem>
                            <menuItem title="Revert to Saved" id="AbR-nh-STx">
                                <modifierMask key="keyEquivalentModifierMask"/>
                                <connections>
                                    <action selector="revertDocumentToSaved:" target="-1" id="Z7W-Dy-oGX"/>
                                </connections>
                            </menuItem>
                            <menuItem isSeparatorItem="YES" id="gmr-39-ik0"/>
                            <menuItem title="Page Setup…" keyEquivalent="P" id="zCv-D3-Oed">
                                <modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
                                <connections>
                                    <action selector="runPageLayout:" target="-1" id="f6F-by-eak"/>
                                </connections>
                            </menuItem>
                            <menuItem title="Print…" keyEquivalent="p" id="0up-Wo-BCZ">
                                <connections>
                                    <action selector="print:" target="-1" id="o5D-gI-3tZ"/>
                                </connections>
                            </menuItem>
                        </items>
                    </menu>
                </menuItem>
                <menuItem title="Edit" id="DyV-jE-06b">
                    <modifierMask key="keyEquivalentModifierMask"/>
                    <menu key="submenu" title="Edit" id="qgZ-Nn-emE">
                        <items>
                            <menuItem title="Undo" keyEquivalent="z" id="fmJ-Sh-KFq">
                                <connections>
                                    <action selector="undo:" target="-1" id="dhq-NH-kMT"/>
                                </connections>
                            </menuItem>
                            <menuItem title="Redo" keyEquivalent="Z" id="d3Q-T5-OU8">
                                <connections>
                                    <action selector="redo:" target="-1" id="0Ea-mD-VTd"/>
                                </connections>
                            </menuItem>
                            <menuItem isSeparatorItem="YES" id="168-9g-WaY"/>
                            <menuItem title="Cut" keyEquivalent="x" id="181-ul-Cau">
                                <connections>
                                    <action selector="cut:" target="-1" id="bDd-4z-sB8"/>
                                </connections>
                            </menuItem>
                            <menuItem title="Copy" keyEquivalent="c" id="P72-WL-Id7">
                                <connections>
                                    <action selector="copy:" target="-1" id="LHS-H6-B8m"/>
                                </connections>
                            </menuItem>
                            <menuItem title="Paste" keyEquivalent="v" id="7lA-qr-RZz">
                                <connections>
                                    <action selector="paste:" target="-1" id="I3E-a3-yfA"/>
                                </connections>
                            </menuItem>
                            <menuItem title="Paste and Match Style" keyEquivalent="V" id="VH9-Yb-tdq">
                                <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
                                <connections>
                                    <action selector="pasteAsPlainText:" target="-1" id="uXH-K8-c7j"/>
                                </connections>
                            </menuItem>
                            <menuItem title="Delete" id="97h-hS-W7v">
                                <modifierMask key="keyEquivalentModifierMask"/>
                                <connections>
                                    <action selector="delete:" target="-1" id="kCg-S1-rx3"/>
                                </connections>
                            </menuItem>
                            <menuItem title="Select All" keyEquivalent="a" id="7Ue-Rt-i6q">
                                <connections>
                                    <action selector="selectAll:" target="-1" id="71A-Ji-sWi"/>
                                </connections>
                            </menuItem>
                            <menuItem isSeparatorItem="YES" id="UPl-Wn-clw"/>
                            <menuItem title="Find" id="CKG-4G-sTK">
                                <modifierMask key="keyEquivalentModifierMask"/>
                                <menu key="submenu" title="Find" id="QNB-no-pUC">
                                    <items>
                                        <menuItem title="Find…" tag="1" keyEquivalent="f" id="nIp-d2-7Zh">
                                            <connections>
                                                <action selector="performFindPanelAction:" target="-1" id="lk6-Ij-CCh"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="LcJ-El-Z1I">
                                            <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
                                            <connections>
                                                <action selector="performFindPanelAction:" target="-1" id="OKz-8m-vYD"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem title="Find Next" tag="2" keyEquivalent="g" id="ye3-hv-uah">
                                            <connections>
                                                <action selector="performFindPanelAction:" target="-1" id="UGh-5H-FfU"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem title="Find Previous" tag="3" keyEquivalent="G" id="4Ml-Pq-s7M">
                                            <connections>
                                                <action selector="performFindPanelAction:" target="-1" id="cTh-EM-FUh"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="v9q-nL-Meb">
                                            <connections>
                                                <action selector="performFindPanelAction:" target="-1" id="9GM-K8-lyI"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem title="Jump to Selection" keyEquivalent="j" id="lp0-ZI-HEt">
                                            <connections>
                                                <action selector="centerSelectionInVisibleArea:" target="-1" id="bqp-Bs-Kqv"/>
                                            </connections>
                                        </menuItem>
                                    </items>
                                </menu>
                            </menuItem>
                            <menuItem title="Spelling and Grammar" id="QaM-Gt-qxC">
                                <modifierMask key="keyEquivalentModifierMask"/>
                                <menu key="submenu" title="Spelling" id="XPN-Gf-Ub5">
                                    <items>
                                        <menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="xZR-tm-YGO">
                                            <connections>
                                                <action selector="showGuessPanel:" target="-1" id="r0Q-e0-GUS"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem title="Check Document Now" keyEquivalent=";" id="0NZ-hG-hjO">
                                            <connections>
                                                <action selector="checkSpelling:" target="-1" id="q6N-Id-SQM"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem isSeparatorItem="YES" id="ePY-aV-eFd"/>
                                        <menuItem title="Check Spelling While Typing" id="ehv-Wy-fN7">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <connections>
                                                <action selector="toggleContinuousSpellChecking:" target="-1" id="2gV-Vb-OEy"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem title="Check Grammar With Spelling" id="0xZ-Ia-9Il">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <connections>
                                                <action selector="toggleGrammarChecking:" target="-1" id="636-D3-CH0"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem title="Correct Spelling Automatically" id="XFQ-kR-keS">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <connections>
                                                <action selector="toggleAutomaticSpellingCorrection:" target="-1" id="TgM-rk-AoE"/>
                                            </connections>
                                        </menuItem>
                                    </items>
                                </menu>
                            </menuItem>
                            <menuItem title="Substitutions" id="fpq-mx-GFH">
                                <modifierMask key="keyEquivalentModifierMask"/>
                                <menu key="submenu" title="Substitutions" id="hpB-LQ-Otm">
                                    <items>
                                        <menuItem title="Show Substitutions" id="aD0-B7-Qm6">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <connections>
                                                <action selector="orderFrontSubstitutionsPanel:" target="-1" id="ZMU-mB-P5B"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem isSeparatorItem="YES" id="fZA-Rd-nGB"/>
                                        <menuItem title="Smart Copy/Paste" id="DhQ-aZ-cnf">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <connections>
                                                <action selector="toggleSmartInsertDelete:" target="-1" id="yqX-YV-7s5"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem title="Smart Quotes" id="CFM-ml-rzh">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <connections>
                                                <action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="MU1-oc-V6i"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem title="Smart Dashes" id="Rwy-4C-iXw">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <connections>
                                                <action selector="toggleAutomaticDashSubstitution:" target="-1" id="MXO-Ph-LaR"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem title="Smart Links" id="ZU5-Ff-QzG">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <connections>
                                                <action selector="toggleAutomaticLinkDetection:" target="-1" id="u8e-8t-zZh"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem title="Data Detectors" id="W5p-Hr-fyj">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <connections>
                                                <action selector="toggleAutomaticDataDetection:" target="-1" id="cyc-P9-hEh"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem title="Text Replacement" id="hse-yi-kCP">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <connections>
                                                <action selector="toggleAutomaticTextReplacement:" target="-1" id="O4g-ls-Fq1"/>
                                            </connections>
                                        </menuItem>
                                    </items>
                                </menu>
                            </menuItem>
                            <menuItem title="Transformations" id="unk-O2-a49">
                                <modifierMask key="keyEquivalentModifierMask"/>
                                <menu key="submenu" title="Transformations" id="UEd-Wj-7xh">
                                    <items>
                                        <menuItem title="Make Upper Case" id="rHW-f0-qPE">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <connections>
                                                <action selector="uppercaseWord:" target="-1" id="o6b-B3-ltZ"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem title="Make Lower Case" id="JW2-j7-JhF">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <connections>
                                                <action selector="lowercaseWord:" target="-1" id="kzy-JU-2nH"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem title="Capitalize" id="Fk4-Z6-v4Q">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <connections>
                                                <action selector="capitalizeWord:" target="-1" id="pGg-wR-daE"/>
                                            </connections>
                                        </menuItem>
                                    </items>
                                </menu>
                            </menuItem>
                            <menuItem title="Speech" id="a0M-EM-ed9">
                                <modifierMask key="keyEquivalentModifierMask"/>
                                <menu key="submenu" title="Speech" id="aKc-Ca-gti">
                                    <items>
                                        <menuItem title="Start Speaking" id="7re-r3-Pro">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <connections>
                                                <action selector="startSpeaking:" target="-1" id="mp3-vG-uGU"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem title="Stop Speaking" id="OId-NR-wtt">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <connections>
                                                <action selector="stopSpeaking:" target="-1" id="gMu-NU-3xC"/>
                                            </connections>
                                        </menuItem>
                                    </items>
                                </menu>
                            </menuItem>
                        </items>
                    </menu>
                </menuItem>
                <menuItem title="Format" id="G1Y-5b-SwM">
                    <modifierMask key="keyEquivalentModifierMask"/>
                    <menu key="submenu" title="Format" id="fcC-AB-A9D">
                        <items>
                            <menuItem title="Font" id="UrC-u6-ETY">
                                <modifierMask key="keyEquivalentModifierMask"/>
                                <menu key="submenu" title="Font" systemMenu="font" id="tHh-IG-GJy">
                                    <items>
                                        <menuItem title="Show Fonts" keyEquivalent="t" id="Mfr-5K-sAH"/>
                                        <menuItem title="Bold" tag="2" keyEquivalent="b" id="2Ti-u0-RUI"/>
                                        <menuItem title="Italic" tag="1" keyEquivalent="i" id="aZg-bT-62m"/>
                                        <menuItem title="Underline" keyEquivalent="u" id="Qr0-xL-Gtm">
                                            <connections>
                                                <action selector="underline:" target="-1" id="9Pq-KC-lhY"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem isSeparatorItem="YES" id="tig-KT-Cyx"/>
                                        <menuItem title="Bigger" tag="3" keyEquivalent="+" id="wfQ-pF-sGc"/>
                                        <menuItem title="Smaller" tag="4" keyEquivalent="-" id="o0g-b5-1TE"/>
                                        <menuItem isSeparatorItem="YES" id="e3c-JQ-Trj"/>
                                        <menuItem title="Kern" id="9kQ-fs-ysx">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <menu key="submenu" title="Kern" id="d3U-kC-afj">
                                                <items>
                                                    <menuItem title="Use Default" id="N4a-9F-H48">
                                                        <modifierMask key="keyEquivalentModifierMask"/>
                                                        <connections>
                                                            <action selector="useStandardKerning:" target="-1" id="mWI-fb-9pd"/>
                                                        </connections>
                                                    </menuItem>
                                                    <menuItem title="Use None" id="3qY-XD-V1H">
                                                        <modifierMask key="keyEquivalentModifierMask"/>
                                                        <connections>
                                                            <action selector="turnOffKerning:" target="-1" id="oTs-bT-o4k"/>
                                                        </connections>
                                                    </menuItem>
                                                    <menuItem title="Tighten" id="Vf5-aq-iBh">
                                                        <modifierMask key="keyEquivalentModifierMask"/>
                                                        <connections>
                                                            <action selector="tightenKerning:" target="-1" id="Gmo-8O-s1w"/>
                                                        </connections>
                                                    </menuItem>
                                                    <menuItem title="Loosen" id="fqw-RD-GHH">
                                                        <modifierMask key="keyEquivalentModifierMask"/>
                                                        <connections>
                                                            <action selector="loosenKerning:" target="-1" id="Xnt-e1-Y30"/>
                                                        </connections>
                                                    </menuItem>
                                                </items>
                                            </menu>
                                        </menuItem>
                                        <menuItem title="Ligatures" id="eWt-hD-tJX">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <menu key="submenu" title="Ligatures" id="BcY-cy-1Gg">
                                                <items>
                                                    <menuItem title="Use Default" id="0jG-MY-iEg">
                                                        <modifierMask key="keyEquivalentModifierMask"/>
                                                        <connections>
                                                            <action selector="useStandardLigatures:" target="-1" id="Uag-2h-94f"/>
                                                        </connections>
                                                    </menuItem>
                                                    <menuItem title="Use None" id="iKs-eL-0pH">
                                                        <modifierMask key="keyEquivalentModifierMask"/>
                                                        <connections>
                                                            <action selector="turnOffLigatures:" target="-1" id="kvi-EJ-8kX"/>
                                                        </connections>
                                                    </menuItem>
                                                    <menuItem title="Use All" id="7jE-bf-gh7">
                                                        <modifierMask key="keyEquivalentModifierMask"/>
                                                        <connections>
                                                            <action selector="useAllLigatures:" target="-1" id="oE0-18-6aq"/>
                                                        </connections>
                                                    </menuItem>
                                                </items>
                                            </menu>
                                        </menuItem>
                                        <menuItem title="Baseline" id="ecg-2N-yBT">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <menu key="submenu" title="Baseline" id="CfO-VG-LUQ">
                                                <items>
                                                    <menuItem title="Use Default" id="ecD-ro-3Lw">
                                                        <modifierMask key="keyEquivalentModifierMask"/>
                                                        <connections>
                                                            <action selector="unscript:" target="-1" id="gRE-pT-XCg"/>
                                                        </connections>
                                                    </menuItem>
                                                    <menuItem title="Superscript" id="pDe-mH-2rr">
                                                        <modifierMask key="keyEquivalentModifierMask"/>
                                                        <connections>
                                                            <action selector="superscript:" target="-1" id="BmN-pQ-6rJ"/>
                                                        </connections>
                                                    </menuItem>
                                                    <menuItem title="Subscript" id="Cgy-US-QV5">
                                                        <modifierMask key="keyEquivalentModifierMask"/>
                                                        <connections>
                                                            <action selector="subscript:" target="-1" id="qY7-KZ-prE"/>
                                                        </connections>
                                                    </menuItem>
                                                    <menuItem title="Raise" id="97t-JI-Vci">
                                                        <modifierMask key="keyEquivalentModifierMask"/>
                                                        <connections>
                                                            <action selector="raiseBaseline:" target="-1" id="AEH-6D-Ze2"/>
                                                        </connections>
                                                    </menuItem>
                                                    <menuItem title="Lower" id="E8f-c5-eLD">
                                                        <modifierMask key="keyEquivalentModifierMask"/>
                                                        <connections>
                                                            <action selector="lowerBaseline:" target="-1" id="L11-ur-xME"/>
                                                        </connections>
                                                    </menuItem>
                                                </items>
                                            </menu>
                                        </menuItem>
                                        <menuItem isSeparatorItem="YES" id="Pqf-Ac-0aL"/>
                                        <menuItem title="Show Colors" keyEquivalent="C" id="wiN-0Q-O1v">
                                            <connections>
                                                <action selector="orderFrontColorPanel:" target="-1" id="ozg-52-WS2"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem isSeparatorItem="YES" id="3tS-37-gSL"/>
                                        <menuItem title="Copy Style" keyEquivalent="c" id="W5h-jb-HUs">
                                            <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
                                            <connections>
                                                <action selector="copyFont:" target="-1" id="70y-2X-DvH"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem title="Paste Style" keyEquivalent="v" id="ad9-Oz-MDe">
                                            <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
                                            <connections>
                                                <action selector="pasteFont:" target="-1" id="e19-oa-chp"/>
                                            </connections>
                                        </menuItem>
                                    </items>
                                </menu>
                            </menuItem>
                            <menuItem title="Text" id="OIj-gh-fhI">
                                <modifierMask key="keyEquivalentModifierMask"/>
                                <menu key="submenu" title="Text" id="Qro-5B-JFR">
                                    <items>
                                        <menuItem title="Align Left" keyEquivalent="{" id="dfU-RW-Wuk">
                                            <connections>
                                                <action selector="alignLeft:" target="-1" id="6s6-M5-3dB"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem title="Center" keyEquivalent="|" id="BiB-dD-IEF">
                                            <connections>
                                                <action selector="alignCenter:" target="-1" id="Ias-ew-2WS"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem title="Justify" id="Vx6-Lf-dBt">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <connections>
                                                <action selector="alignJustified:" target="-1" id="Cnd-95-vjd"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem title="Align Right" keyEquivalent="}" id="Iaa-As-jVR">
                                            <connections>
                                                <action selector="alignRight:" target="-1" id="QS8-5b-Yp9"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem isSeparatorItem="YES" id="yIX-7f-18k"/>
                                        <menuItem title="Writing Direction" id="Iwb-Am-7cv">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <menu key="submenu" title="Writing Direction" id="6xl-tS-H9e">
                                                <items>
                                                    <menuItem title="Paragraph" enabled="NO" id="dhh-aS-WGH">
                                                        <modifierMask key="keyEquivalentModifierMask"/>
                                                    </menuItem>
                                                    <menuItem id="cqw-Km-0ZA">
                                                        <string key="title">	Default</string>
                                                        <modifierMask key="keyEquivalentModifierMask"/>
                                                        <connections>
                                                            <action selector="makeBaseWritingDirectionNatural:" target="-1" id="hTj-h3-Dbw"/>
                                                        </connections>
                                                    </menuItem>
                                                    <menuItem id="0AN-EA-sbS">
                                                        <string key="title">	Left to Right</string>
                                                        <modifierMask key="keyEquivalentModifierMask"/>
                                                        <connections>
                                                            <action selector="makeBaseWritingDirectionLeftToRight:" target="-1" id="PvD-3f-fLh"/>
                                                        </connections>
                                                    </menuItem>
                                                    <menuItem id="Urq-dN-eDm">
                                                        <string key="title">	Right to Left</string>
                                                        <modifierMask key="keyEquivalentModifierMask"/>
                                                        <connections>
                                                            <action selector="makeBaseWritingDirectionRightToLeft:" target="-1" id="wCK-bQ-kt8"/>
                                                        </connections>
                                                    </menuItem>
                                                    <menuItem isSeparatorItem="YES" id="rk9-8B-hH0"/>
                                                    <menuItem title="Selection" enabled="NO" id="5oD-c2-YaH">
                                                        <modifierMask key="keyEquivalentModifierMask"/>
                                                    </menuItem>
                                                    <menuItem id="TwC-9c-uu6">
                                                        <string key="title">	Default</string>
                                                        <modifierMask key="keyEquivalentModifierMask"/>
                                                        <connections>
                                                            <action selector="makeTextWritingDirectionNatural:" target="-1" id="CJT-Ti-Pcj"/>
                                                        </connections>
                                                    </menuItem>
                                                    <menuItem id="ei1-Nz-VaB">
                                                        <string key="title">	Left to Right</string>
                                                        <modifierMask key="keyEquivalentModifierMask"/>
                                                        <connections>
                                                            <action selector="makeTextWritingDirectionLeftToRight:" target="-1" id="1aT-ds-P4d"/>
                                                        </connections>
                                                    </menuItem>
                                                    <menuItem id="gTl-D2-5nm">
                                                        <string key="title">	Right to Left</string>
                                                        <modifierMask key="keyEquivalentModifierMask"/>
                                                        <connections>
                                                            <action selector="makeTextWritingDirectionRightToLeft:" target="-1" id="iCN-qD-TNB"/>
                                                        </connections>
                                                    </menuItem>
                                                </items>
                                            </menu>
                                        </menuItem>
                                        <menuItem isSeparatorItem="YES" id="v6h-Km-CDW"/>
                                        <menuItem title="Show Ruler" id="KMn-rP-pjX">
                                            <modifierMask key="keyEquivalentModifierMask"/>
                                            <connections>
                                                <action selector="toggleRuler:" target="-1" id="tbU-4g-dIN"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem title="Copy Ruler" keyEquivalent="c" id="9PU-a9-pa5">
                                            <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
                                            <connections>
                                                <action selector="copyRuler:" target="-1" id="BON-cn-8km"/>
                                            </connections>
                                        </menuItem>
                                        <menuItem title="Paste Ruler" keyEquivalent="v" id="Psd-Hu-DQ2">
                                            <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
                                            <connections>
                                                <action selector="pasteRuler:" target="-1" id="olo-DZ-QLd"/>
                                            </connections>
                                        </menuItem>
                                    </items>
                                </menu>
                            </menuItem>
                        </items>
                    </menu>
                </menuItem>
                <menuItem title="View" id="9e9-vl-rMp">
                    <modifierMask key="keyEquivalentModifierMask"/>
                    <menu key="submenu" title="View" id="Ufi-lb-m6J">
                        <items>
                            <menuItem title="Show Toolbar" keyEquivalent="t" id="9uk-f8-cFm">
                                <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
                                <connections>
                                    <action selector="toggleToolbarShown:" target="-1" id="zlm-Zt-usB"/>
                                </connections>
                            </menuItem>
                            <menuItem title="Customize Toolbar…" id="WXO-Z7-zt9">
                                <modifierMask key="keyEquivalentModifierMask"/>
                                <connections>
                                    <action selector="runToolbarCustomizationPalette:" target="-1" id="VfH-K3-KqQ"/>
                                </connections>
                            </menuItem>
                        </items>
                    </menu>
                </menuItem>
                <menuItem title="Window" id="V5i-yJ-hYY">
                    <modifierMask key="keyEquivalentModifierMask"/>
                    <menu key="submenu" title="Window" systemMenu="window" id="qbq-Fv-HFU">
                        <items>
                            <menuItem title="Minimize" keyEquivalent="m" id="iBF-A1-fu7">
                                <connections>
                                    <action selector="performMiniaturize:" target="-1" id="vc3-Wg-Bp8"/>
                                </connections>
                            </menuItem>
                            <menuItem title="Zoom" id="N6d-4e-0TH">
                                <modifierMask key="keyEquivalentModifierMask"/>
                                <connections>
                                    <action selector="performZoom:" target="-1" id="fKw-5N-G5L"/>
                                </connections>
                            </menuItem>
                            <menuItem isSeparatorItem="YES" id="0HB-oG-tGt"/>
                            <menuItem title="Bring All to Front" id="9gO-FD-Hbg">
                                <modifierMask key="keyEquivalentModifierMask"/>
                                <connections>
                                    <action selector="arrangeInFront:" target="-1" id="E96-rY-gTU"/>
                                </connections>
                            </menuItem>
                        </items>
                    </menu>
                </menuItem>
                <menuItem title="Help" id="JsN-sb-abl">
                    <modifierMask key="keyEquivalentModifierMask"/>
                    <menu key="submenu" title="Help" systemMenu="help" id="a0m-He-GWY">
                        <items>
                            <menuItem title="Nimbus Help" keyEquivalent="?" id="bBd-XM-WKa">
                                <connections>
                                    <action selector="showHelp:" target="-1" id="NMF-47-5fi"/>
                                </connections>
                            </menuItem>
                        </items>
                    </menu>
                </menuItem>
            </items>
        </menu>
    </objects>
</document>


================================================
FILE: NimbusMenuBarApp/Nimbus/Nimbus/Images.xcassets/AppIcon.appiconset/Contents.json
================================================
{
  "images" : [
    {
      "size" : "16x16",
      "idiom" : "mac",
      "filename" : "icon_16x16.png",
      "scale" : "1x"
    },
    {
      "size" : "16x16",
      "idiom" : "mac",
      "filename" : "icon_16x16@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "32x32",
      "idiom" : "mac",
      "filename" : "icon_32x32.png",
      "scale" : "1x"
    },
    {
      "size" : "32x32",
      "idiom" : "mac",
      "filename" : "icon_32x32@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "128x128",
      "idiom" : "mac",
      "filename" : "icon_128x128.png",
      "scale" : "1x"
    },
    {
      "size" : "128x128",
      "idiom" : "mac",
      "filename" : "icon_128x128@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "256x256",
      "idiom" : "mac",
      "filename" : "icon_256x256.png",
      "scale" : "1x"
    },
    {
      "size" : "256x256",
      "idiom" : "mac",
      "filename" : "icon_256x256@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "512x512",
      "idiom" : "mac",
      "filename" : "icon_512x512.png",
      "scale" : "1x"
    },
    {
      "size" : "512x512",
      "idiom" : "mac",
      "filename" : "icon_512x512@2x.png",
      "scale" : "2x"
    }
  ],
  "info" : {
    "version" : 1,
    "author" : "xcode"
  }
}

================================================
FILE: NimbusMenuBarApp/Nimbus/Nimbus/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>en</string>
	<key>CFBundleExecutable</key>
	<string>${EXECUTABLE_NAME}</string>
	<key>CFBundleIconFile</key>
	<string></string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>${PRODUCT_NAME}</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0</string>
	<key>CFBundleSignature</key>
	<string>????</string>
	<key>CFBundleVersion</key>
	<string>1</string>
	<key>LSMinimumSystemVersion</key>
	<string>${MACOSX_DEPLOYMENT_TARGET}</string>
	<key>LSUIElement</key>
	<true/>
	<key>NSHumanReadableCopyright</key>
	<string>Copyright © 2014 Ethanal. All rights reserved.</string>
	<key>NSMainNibFile</key>
	<string>MainMenu</string>
	<key>NSPrincipalClass</key>
	<string>NSApplication</string>
</dict>
</plist>


================================================
FILE: NimbusMenuBarApp/Nimbus/Nimbus/KeychainManager.swift
================================================

import Cocoa
import Security

// Identifiers
let serviceIdentifier = "Nimbus"
let userAccount = "authenticatedUser"
let accessGroup = "Nimbus"

// Arguments for the keychain queries
let kSecClassValue = NSString(format: kSecClass)
let kSecAttrAccountValue = NSString(format: kSecAttrAccount)
let kSecValueDataValue = NSString(format: kSecValueData)
let kSecClassGenericPasswordValue = NSString(format: kSecClassGenericPassword)
let kSecAttrServiceValue = NSString(format: kSecAttrService)
let kSecMatchLimitValue = NSString(format: kSecMatchLimit)
let kSecReturnDataValue = NSString(format: kSecReturnData)
let kSecMatchLimitOneValue = NSString(format: kSecMatchLimitOne)

class KeychainService: NSObject {
    
    /**
    * Exposed methods to perform queries.
    * Note: feel free to play around with the arguments
    * for these if you want to be able to customise the
    * service identifier, user accounts, access groups, etc.
    */
    internal class func saveToken(token: NSString) {
        self.save(serviceIdentifier, data: token)
    }
    
    internal class func loadToken() -> NSString? {
        let token = self.load(serviceIdentifier)
        
        return token
    }
    
    /**
    * Internal methods for querying the keychain.
    */
    private class func save(service: NSString, data: NSString) {
        let dataFromString: NSData = data.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
        
        // Instantiate a new default keychain query
        let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, userAccount, dataFromString], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecValueDataValue])
        
        // Delete any existing items
        SecItemDelete(keychainQuery as CFDictionaryRef)
        
        // Add the new keychain item
        let status: OSStatus = SecItemAdd(keychainQuery as CFDictionaryRef, nil)
        if status != errSecSuccess {
            print("Error saving to keychain")
        }
    }
    
    private class func load(service: NSString) -> NSString? {
        // Instantiate a new default keychain query
        // Tell the query to return a result
        // Limit our results to one item
        let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, userAccount, kCFBooleanTrue, kSecMatchLimitOneValue], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue, kSecMatchLimitValue])
        
        var extractedData : AnyObject?
        
        // Search for the keychain items
        let status: OSStatus = SecItemCopyMatching(keychainQuery, &extractedData)
        
        var contentsOfKeychain: NSString?
        
        if let retrievedData = extractedData as? NSData where status == errSecSuccess {
            // Convert the data retrieved from the keychain into a string
            contentsOfKeychain = NSString(data: retrievedData, encoding: NSUTF8StringEncoding)
        } else {
            print("Nothing was retrieved from the keychain. Status code \(status)")
        }
        
        return contentsOfKeychain
    }
}

================================================
FILE: NimbusMenuBarApp/Nimbus/Nimbus/Nimbus-Bridging-Header.h
================================================
//
//  Use this file to import your target's public headers that you would like to expose to Swift.
//

#import "ScreenshotWatcher.h"

================================================
FILE: NimbusMenuBarApp/Nimbus/Nimbus/PreferencesManager.swift
================================================
//
//  PreferencesManager.swift
//  Nimbus
//
//  Created by Ethan Lowman on 7/25/14.
//  Copyright (c) 2014 Ethanal. All rights reserved.
//

import Cocoa

class PreferencesManager: NSObject {
    var prefs = NSUserDefaults.standardUserDefaults()
    
    var hostname: String {
        get {
            if let val = prefs.stringForKey("hostname") as String! {
                return val
            }
            return "example.com"
        }
        set {
            prefs.setObject(newValue, forKey: "hostname")
        }
    }
    
    var loggedIn: Bool {
        get {
            if let val = prefs.boolForKey("loggedIn") as Bool! {
                return val
            }
            return false
        }
        set {
            prefs.setBool(newValue, forKey: "loggedIn")
        }
    }
    
    var username: String {
        get {
            if let val = prefs.stringForKey("username") as String! {
                return val
            }
            return ""
        }
        set {
            prefs.setValue(newValue, forKey: "username")
        }
    }
    
    
    var uploadScreenshots: Int {
        get {
            if let val = prefs.integerForKey("uploadScreenshots") as Int! {
                return val
            }
            return 1
        }
        set {
            prefs.setInteger(newValue, forKey: "uploadScreenshots")
        }
    }
    
}


================================================
FILE: NimbusMenuBarApp/Nimbus/Nimbus/PreferencesWindowController.swift
================================================
//
//  PreferencesWindowController.swift
//  Nimbus
//
//  Created by Ethan Lowman on 7/25/14.
//  Copyright (c) 2014 Ethanal. All rights reserved.
//

import Cocoa

class PreferencesWindowController: NSWindowController, NSWindowDelegate {
    var prefs = PreferencesManager()
    var api = APIClient()
    
    @IBOutlet weak var hostnameField: NSTextField?
    @IBOutlet weak var usernameField: NSTextField?
    @IBOutlet weak var passwordField: NSSecureTextField?
    @IBOutlet weak var uploadScreenshotsCheckbox: NSButton?
    @IBOutlet weak var accountActionButton: NSButton?
    @IBOutlet weak var hostnameLabel: NSTextField?
    @IBOutlet weak var usernameLabel: NSTextField?
    @IBOutlet weak var passwordLabel: NSTextField?
    
    @IBOutlet var appMenu: NSMenu!
    
    override func windowDidLoad() {
        super.windowDidLoad()
        updateAccountUI()
    }
    
    @IBAction func uploadScreenshotsCheckboxPressed(sender: AnyObject) {
        prefs.uploadScreenshots = uploadScreenshotsCheckbox!.state
    }

    @IBAction func accountActionButtonPressed(sender: AnyObject) {
        if !prefs.loggedIn {
            prefs.hostname = hostnameField!.stringValue
            prefs.username = usernameField!.stringValue
            
            api.getTokenForUsername(usernameField!.stringValue, password: passwordField!.stringValue, successCallback: {(token: NSString!) -> Void in
                    self.prefs.loggedIn = true
                    KeychainService.saveToken(token)

                    self.updateAccountUI()
                }, errorCallback: {() -> Void in
                    let alert = NSAlert()
                    alert.messageText = "Unable to login with provided credentials"
                    alert.runModal()
                })
            
            
        } else {
            prefs.loggedIn = false
            updateAccountUI()
        }
        
        
    }
    
    func updateAccountUI() {
        uploadScreenshotsCheckbox!.state = prefs.uploadScreenshots

        if prefs.loggedIn {
            accountActionButton!.title = "Logout"
            
            hostnameField!.hidden = true
            hostnameLabel!.hidden = false
            hostnameLabel!.stringValue = prefs.hostname
            
            usernameField!.hidden = true
            usernameLabel!.hidden = false
            usernameLabel!.stringValue = prefs.username
            
            passwordField!.hidden = true
            passwordLabel!.hidden = true
            
        } else {
            accountActionButton!.title = "Login"
            
            hostnameField!.hidden = false
            hostnameField!.stringValue = ""
            hostnameLabel!.hidden = true
            
            usernameField!.hidden = false
            usernameField!.stringValue = ""
            usernameLabel!.hidden = true
            
            passwordField!.hidden = false
            passwordField!.stringValue = ""
            passwordLabel!.hidden = false
        }
    }
    
    
    
}


================================================
FILE: NimbusMenuBarApp/Nimbus/Nimbus/PreferencesWindowController.xib
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6254" systemVersion="14C109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
    <dependencies>
        <deployment identifier="macosx"/>
        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6254"/>
    </dependencies>
    <objects>
        <customObject id="-2" userLabel="File's Owner" customClass="PreferencesWindowController" customModule="Nimbus" customModuleProvider="target">
            <connections>
                <outlet property="accountActionButton" destination="UjG-7X-3hu" id="3Wb-Hf-8nj"/>
                <outlet property="hostnameField" destination="r7s-Xv-pFO" id="aCi-Hz-xmd"/>
                <outlet property="hostnameLabel" destination="l4j-7h-KDy" id="fpN-Nw-vaQ"/>
                <outlet property="passwordField" destination="TkR-l3-2EX" id="Tm3-ZR-hzY"/>
                <outlet property="passwordLabel" destination="TR0-Ny-FOc" id="Bmp-vE-pE8"/>
                <outlet property="uploadScreenshotsCheckbox" destination="s89-w0-n9y" id="eoF-mX-cKh"/>
                <outlet property="usernameField" destination="ZH0-ad-kpJ" id="l2e-rk-lQq"/>
                <outlet property="usernameLabel" destination="bvA-pA-hcL" id="hbL-cX-7rV"/>
                <outlet property="window" destination="F0z-JX-Cv5" id="gIp-Ho-8D9"/>
            </connections>
        </customObject>
        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
        <customObject id="-3" userLabel="Application" customClass="NSObject"/>
        <window title="Preferences" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" animationBehavior="default" id="F0z-JX-Cv5">
            <windowStyleMask key="styleMask" titled="YES" closable="YES"/>
            <rect key="contentRect" x="196" y="240" width="303" height="187"/>
            <rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
            <view key="contentView" id="se5-gp-TjO">
                <rect key="frame" x="0.0" y="0.0" width="303" height="187"/>
                <autoresizingMask key="autoresizingMask"/>
                <subviews>
                    <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8qV-BX-YSG">
                        <rect key="frame" x="18" y="147" width="72" height="17"/>
                        <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Hostname:" id="6EW-XZ-sfN">
                            <font key="font" metaFont="system"/>
                            <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
                            <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
                        </textFieldCell>
                    </textField>
                    <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Vtk-rD-6Sw">
                        <rect key="frame" x="19" y="117" width="71" height="17"/>
                        <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Username:" id="WBg-rY-sRf">
                            <font key="font" metaFont="system"/>
                            <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
                            <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
                        </textFieldCell>
                    </textField>
                    <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="TR0-Ny-FOc">
                        <rect key="frame" x="24" y="85" width="66" height="17"/>
                        <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Password:" id="tbC-6h-ka6">
                            <font key="font" metaFont="system"/>
                            <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
                            <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
                        </textFieldCell>
                    </textField>
                    <textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="r7s-Xv-pFO">
                        <rect key="frame" x="96" y="145" width="187" height="22"/>
                        <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" alignment="left" placeholderString="" drawsBackground="YES" id="vhC-VW-U1U">
                            <font key="font" metaFont="system"/>
                            <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
                            <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
                        </textFieldCell>
                    </textField>
                    <secureTextField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="TkR-l3-2EX">
                        <rect key="frame" x="96" y="83" width="187" height="22"/>
                        <secureTextFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" drawsBackground="YES" usesSingleLineMode="YES" id="Ly3-lX-Gdc">
                            <font key="font" metaFont="system"/>
                            <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
                            <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
                            <allowedInputSourceLocales>
                                <string>NSAllRomanInputSourcesLocaleIdentifier</string>
                            </allowedInputSourceLocales>
                        </secureTextFieldCell>
                    </secureTextField>
                    <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="UjG-7X-3hu">
                        <rect key="frame" x="207" y="47" width="75" height="32"/>
                        <buttonCell key="cell" type="push" title="Login" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="HXa-qJ-WsE">
                            <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
                            <font key="font" metaFont="system"/>
                        </buttonCell>
                        <connections>
                            <action selector="accountActionButtonPressed:" target="-2" id="lVz-g8-Fgi"/>
                        </connections>
                    </button>
                    <textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ZH0-ad-kpJ">
                        <rect key="frame" x="96" y="115" width="187" height="22"/>
                        <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" alignment="left" placeholderString="" drawsBackground="YES" id="BrX-eL-YLY">
                            <font key="font" metaFont="system"/>
                            <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
                            <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
                        </textFieldCell>
                    </textField>
                    <textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bvA-pA-hcL">
                        <rect key="frame" x="99" y="117" width="186" height="17"/>
                        <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="username" id="Gd6-bB-49G">
                            <font key="font" metaFont="system"/>
                            <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
                            <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
                        </textFieldCell>
                    </textField>
                    <textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="l4j-7h-KDy">
                        <rect key="frame" x="99" y="147" width="186" height="17"/>
                        <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="hostname" id="sct-P4-9oO">
                            <font key="font" metaFont="system"/>
                            <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
                            <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
                        </textFieldCell>
                    </textField>
                    <button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="s89-w0-n9y">
                        <rect key="frame" x="94" y="18" width="147" height="18"/>
                        <buttonCell key="cell" type="check" title="Upload Screenshots" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="6Bw-6k-7Le">
                            <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
                            <font key="font" metaFont="system"/>
                        </buttonCell>
                        <connections>
                            <action selector="uploadScreenshotsCheckboxPressed:" target="-2" id="cB0-69-Txb"/>
                        </connections>
                    </button>
                </subviews>
            </view>
            <connections>
                <outlet property="delegate" destination="-2" id="0bl-1N-AYu"/>
            </connections>
            <point key="canvasLocation" x="332.5" y="216.5"/>
        </window>
    </objects>
</document>


================================================
FILE: NimbusMenuBarApp/Nimbus/Nimbus/ScreenshotWatcher.h
================================================
//
//  ScreenshotWatch.h
//  Nimbus
//
//  Created by Ethan Lowman on 7/27/14.
//  Copyright (c) 2014 Ethanal. All rights reserved.
//

#import <Foundation/Foundation.h>

typedef void(^FileCallbackBlock)(NSData*, NSString*);

@interface ScreenshotWatcher : NSObject

@property (nonatomic, copy) FileCallbackBlock uploadCallback;

- (instancetype)initWithUploadFileCallback:(FileCallbackBlock)callback;
- (void)startWatchingPath:(NSString*) path;

@end


================================================
FILE: NimbusMenuBarApp/Nimbus/Nimbus/ScreenshotWatcher.m
================================================
//
//  ScreenshotWatch.m
//  Nimbus
//
//  Created by Ethan Lowman on 7/27/14.
//  Copyright (c) 2014 Ethanal. All rights reserved.
//

#import "ScreenshotWatcher.h"


@interface ScreenshotWatcher()

@property (nonatomic, assign) FSEventStreamRef eventStream;

@end

static NSMutableSet *uploadedFilenames;

@implementation ScreenshotWatcher

- (instancetype)initWithUploadFileCallback:(FileCallbackBlock) callback {
    self = [super init];
    if (self) {
        if (uploadedFilenames == nil) {
            uploadedFilenames = [NSMutableSet new];
        }
        
        self.uploadCallback = callback;
    }
    return self;
}

void describeFSEvent(FSEventStreamEventFlags eventFlags) {
    // if (eventFlags & kFSEventStreamEventFlagNone)               printf("None\t");
    if (eventFlags & kFSEventStreamEventFlagMustScanSubDirs)    printf("MustScanSubDirs\t");
    if (eventFlags & kFSEventStreamEventFlagUserDropped)        printf("UserDropped\t");
    if (eventFlags & kFSEventStreamEventFlagKernelDropped)      printf("KernelDropped\t");
    if (eventFlags & kFSEventStreamEventFlagEventIdsWrapped)    printf("EventIdsWrapped\t");
    if (eventFlags & kFSEventStreamEventFlagHistoryDone)        printf("HistoryDone\t");
    if (eventFlags & kFSEventStreamEventFlagRootChanged)        printf("RootChanged\t");
    if (eventFlags & kFSEventStreamEventFlagMount)              printf("Mount\t");
    if (eventFlags & kFSEventStreamEventFlagUnmount)            printf("Unmount\t");
    if (eventFlags & kFSEventStreamEventFlagItemCreated)        printf("ItemCreated\t");
    if (eventFlags & kFSEventStreamEventFlagItemRemoved)        printf("ItemRemoved\t");
    if (eventFlags & kFSEventStreamEventFlagItemInodeMetaMod)   printf("ItemInodeMetaMod\t");
    if (eventFlags & kFSEventStreamEventFlagItemRenamed)        printf("ItemRenamed\t");
    if (eventFlags & kFSEventStreamEventFlagItemModified)       printf("ItemModified\t");
    if (eventFlags & kFSEventStreamEventFlagItemFinderInfoMod)  printf("ItemFinderInfoMod\t");
    if (eventFlags & kFSEventStreamEventFlagItemChangeOwner)    printf("ItemChangeOwner\t");
    if (eventFlags & kFSEventStreamEventFlagItemXattrMod)       printf("ItemXattrMod\t");
    if (eventFlags & kFSEventStreamEventFlagItemIsFile)         printf("ItemIsFile\t");
    if (eventFlags & kFSEventStreamEventFlagItemIsDir)          printf("ItemIsDir\t");
    if (eventFlags & kFSEventStreamEventFlagItemIsSymlink)      printf("ItemIsSymlink\t");
    printf("\n");
}


void fsEventsCallback(ConstFSEventStreamRef streamRef,
                      void *info,
                      size_t numEvents,
                      void *eventPaths,
                      const FSEventStreamEventFlags eventFlags[],
                      const FSEventStreamEventId eventIds[])
{
    char** paths = (char**)eventPaths;
    
    for (int i = 0; i < numEvents; i++) {
        NSString *path = [NSString stringWithUTF8String:paths[i]];
        NSURL *fileURL = [NSURL fileURLWithPath:path];
        NSFileManager *fileManager = [NSFileManager defaultManager];
        
//        if (![fileURL.lastPathComponent hasPrefix:@"."]) {
//            printf("%s\t", [fileURL.lastPathComponent cStringUsingEncoding:NSUTF8StringEncoding]);
//            describeFSEvent(eventFlags[i]);
//        }
        
        if ((eventFlags[i] & kFSEventStreamEventFlagItemRenamed) &&
            (eventFlags[i] & kFSEventStreamEventFlagItemXattrMod) &&
            !([uploadedFilenames containsObject:path])) {
            
            if (![fileURL.lastPathComponent hasPrefix:@"."]) {
                NSMetadataItem *metadata = [[NSMetadataItem alloc] initWithURL:fileURL];
                if ([fileManager fileExistsAtPath:path]) {
                    BOOL isScreenshot = [[metadata valueForAttribute:@"kMDItemIsScreenCapture"] integerValue] == 1;
                    if (isScreenshot) {
//                        printf("Uploading %s\n", [fileURL.lastPathComponent cStringUsingEncoding:NSUTF8StringEncoding]);
                        NSData *fileData = [fileManager contentsAtPath:path];
                        ScreenshotWatcher *watcher = (__bridge ScreenshotWatcher*)info;
                        watcher.uploadCallback(fileData, path);
                        
                        [uploadedFilenames addObject:path];
                    }
                }
            }

        }
        
    }
}

- (void)startWatchingPath:(NSString*) path {
    FSEventStreamContext context;
    context.info = (__bridge void *)(self);
    context.version = 0;
    context.retain = NULL;
    context.release = NULL;
    context.copyDescription = NULL;
    
    NSArray *pathsToWatch = @[path];
    
    self.eventStream = FSEventStreamCreate(NULL,
                                           &fsEventsCallback,
                                           &context,
                                           (__bridge CFArrayRef)(pathsToWatch),
                                           kFSEventStreamEventIdSinceNow,
                                           0.1,
                                           kFSEventStreamCreateFlagFileEvents
                                           );
    
    FSEventStreamScheduleWithRunLoop(self.eventStream, CFRunLoopGetCurrent(),
                                     kCFRunLoopDefaultMode);
    FSEventStreamStart(self.eventStream);
}

@end

================================================
FILE: NimbusMenuBarApp/Nimbus/Nimbus/StatusItemView.swift
================================================
//
//  StatusItemView.swift
//  Nimbus
//
//  Created by Ethan Lowman on 7/25/14.
//  Copyright (c) 2014 Ethanal. All rights reserved.
//

import Cocoa

enum StatusItemViewStatus: String {
    case Normal = "Normal"
    case Error = "Error"
    case Success = "Success"
    case Working = "Working"
}

func delay(delay:Double, closure:()->()) {
    dispatch_after(
        dispatch_time(
            DISPATCH_TIME_NOW,
            Int64(delay * Double(NSEC_PER_SEC))
        ),
        dispatch_get_main_queue(), closure)
}

class StatusItemView: NSView, NSMenuDelegate, NSWindowDelegate {
    let statusItemWidth = 30.0
    let statusBarHeight = NSStatusBar.systemStatusBar().thickness
    let statusItemRect: NSRect
    
    var status: StatusItemViewStatus = .Normal {
    didSet {
        switch status {
        case .Normal:
            progressFrame = 0
        case .Working:
            if oldValue != .Working {
                progressFrame = 1
            }
        default:
            progressFrame = 0
            
            let statusAtDispatch = status
            delay(2.0) {
                if statusAtDispatch == self.status {
                    self.status = .Normal
                    self.updateUI()
                }
            }
        }
        self.updateUI()
    }
    }
    
    var active: Bool = false {
    didSet {
        self.updateUI()
    }
    }
    
    var progressFrame: Int = 0 {
    didSet {
        if self.progressFrame != 0 {
            delay(0.25) {
                if self.progressFrame != 0 {
                    self.progressFrame = 1 + (self.progressFrame % 3)
                }
            }
        }

        updateUI()
    }
    }
    
    
    var statusItem: NSStatusItem
    var popover: NSPopover?
    var preferencesWindowController: NSWindowController?
    
    var prefs = PreferencesManager()
    
    lazy var imageView: NSImageView = {
        let view:NSImageView = NSImageView(frame: self.statusItemRect)
        view.unregisterDraggedTypes()
        return view
        }()
    
    lazy var statusItemMenu: NSMenu = {
        let menu: NSMenu = NSMenu()
        menu.delegate = self
        menu.autoenablesItems = true
        menu.addItem(self.websiteItem)
        menu.addItem(self.preferencesItem)
        menu.addItem(self.quitItem)
        return menu
        }()
    
    lazy var websiteItem: NSMenuItem = {
        let item = NSMenuItem(title: "Launch Nimbus Website", action: "openWebsite:", keyEquivalent: "")
        item.target = self
        return item
        }()
    
    lazy var preferencesItem: NSMenuItem = {
        let item = NSMenuItem(title: "Preferences", action: "openPreferences:", keyEquivalent: "")
        item.target = self
        return item
        }()
    
    lazy var quitItem: NSMenuItem = {
        let item = NSMenuItem(title: "Quit", action: "quitApp:", keyEquivalent: "")
        item.target = self
        return item
        }()
    
    
    
    init() {
        statusItemRect = NSMakeRect(0, 0, CGFloat(statusItemWidth), CGFloat(statusBarHeight))
        statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(CGFloat(statusItemWidth))
        
        super.init(frame: statusItemRect)
        
        statusItem.view = self
        statusItem.menu = statusItemMenu
        self.addSubview(imageView)
        
        self.registerForDraggedTypes([NSFilenamesPboardType, NSURLPboardType, NSStringPboardType])
        
        updateUI()
    }

    required convenience init?(coder: NSCoder) {
        self.init()
    }

    override func drawRect(dirtyRect: NSRect) {
        if active {
            NSColor.selectedMenuItemColor().setFill()
            NSRectFill(dirtyRect)
        } else {
            NSColor.clearColor().setFill()
            NSRectFill(dirtyRect)
        }
    }
    
    func updateUI() {
        if (active) {
            self.imageView.image = NSImage(named: "menubar-highlighted")
        } else {
            if status != .Working {
                var imageNames: Dictionary<StatusItemViewStatus, String> = [
                    .Normal: "menubar",
                    .Error: "menubar-error",
                    .Success: "menubar-success",
                ]
                self.imageView.image = NSImage(named: imageNames[status]!)
            } else {
                self.imageView.image = NSImage(named: "menubar-progress-\(progressFrame)")
            }
        }
        
        self.needsDisplay = true
    }
    
    override func mouseDown(theEvent: NSEvent) {
        active = true
        statusItem.popUpStatusItemMenu(statusItemMenu)
    }
    
    override func mouseUp(theEvent: NSEvent) {
        active = false
    }
    
    func openWebsite(sender: NSStatusItem!) {
        NSWorkspace.sharedWorkspace().openURL(NSURL(string: "http://account." + prefs.hostname)!)
    }
    
    func openPreferences(sender: NSStatusItem!) {
        if !(preferencesWindowController != nil) {
            preferencesWindowController = PreferencesWindowController(windowNibName: "PreferencesWindowController")
        }
        preferencesWindowController!.showWindow(self)
        NSApp.activateIgnoringOtherApps(true)
    }
    
    func quitApp(sender: NSStatusItem!) {
        NSApplication.sharedApplication().terminate(nil)
    }
    
    // NSMenuDelegate
    func menuDidClose(menu: NSMenu) {
        active = false
    }
    
    override func draggingEntered(sender: NSDraggingInfo) -> NSDragOperation {
        return .Copy
    }
    
    override func draggingEnded(sender: NSDraggingInfo!) {
        if NSPointInRect(sender.draggingLocation(), self.frame) {
            handleDrop(sender)
        }
    }
    
    // Manually called instead of performDragOperation
    // http://openradar.appspot.com/radar?id=1745403
    func handleDrop(sender: NSDraggingInfo!) -> Bool {
        let pboard = sender.draggingPasteboard();
        let types: NSArray = pboard.types!
        let appDelegate = NSApplication.sharedApplication().delegate as! AppDelegate
        let fileManager = NSFileManager.defaultManager()
        
        if types.containsObject(NSFilenamesPboardType) {
            let fileURL = NSURL(fromPasteboard: pboard)!
            let fileData = fileManager.contentsAtPath(fileURL.path!)
            let fileName = fileURL.lastPathComponent
            
            if (fileData != nil) && (fileName != nil) {
                appDelegate.uploadFile(fileData!, filename: fileName!)
            } else {
                status = .Error
            }
            
        } else if types.containsObject(NSURLPboardType) {
            let url = NSURL(fromPasteboard: pboard)!
            appDelegate.uploadLink(url)
            
        } else if types.containsObject(NSStringPboardType) {
            let text = pboard.stringForType(NSStringPboardType) as NSString!
            
            let legalChars = NSMutableCharacterSet.alphanumericCharacterSet()
            legalChars.formUnionWithCharacterSet(NSCharacterSet.whitespaceCharacterSet())
            legalChars.invert()
            var filename = (text.componentsSeparatedByCharactersInSet(legalChars) as NSArray).componentsJoinedByString("") as NSString
            filename = filename.substringToIndex(30 > text.length ? text.length : 30) + ".txt"
            
            let fileData = text.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)
            
            if let d = fileData {
                    appDelegate.uploadFile(d, filename: filename as String)
            } else {
                status = .Error
            }
        }
        
        return true;
    }
    
}


================================================
FILE: NimbusMenuBarApp/Nimbus/Nimbus/SwiftyJSON.swift
================================================
//  SwiftyJSON.swift
//
//  Copyright (c) 2014 Ruoyu Fu, Pinglin Tang
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.

import Foundation

// MARK: - Error

///Error domain
public let ErrorDomain: String = "SwiftyJSONErrorDomain"

///Error code
public let ErrorUnsupportedType: Int = 999
public let ErrorIndexOutOfBounds: Int = 900
public let ErrorWrongType: Int = 901
public let ErrorNotExist: Int = 500
public let ErrorInvalidJSON: Int = 490

// MARK: - JSON Type

/**
JSON's type definitions.

See http://www.json.org
*/
public enum Type :Int{
    
    case Number
    case String
    case Bool
    case Array
    case Dictionary
    case Null
    case Unknown
}

// MARK: - JSON Base

public struct JSON {
    
    /**
     Creates a JSON using the data.
     
     - parameter data:  The NSData used to convert to json.Top level object in data is an NSArray or NSDictionary
     - parameter opt:   The JSON serialization reading options. `.AllowFragments` by default.
     - parameter error: error The NSErrorPointer used to return the error. `nil` by default.
     
     - returns: The created JSON
     */
    public init(data:NSData, options opt: NSJSONReadingOptions = .AllowFragments, error: NSErrorPointer = nil) {
        do {
            let object: AnyObject = try NSJSONSerialization.JSONObjectWithData(data, options: opt)
            self.init(object)
        } catch let aError as NSError {
            if error != nil {
                error.memory = aError
            }
            self.init(NSNull())
        }
    }
    
    /**
     Create a JSON from JSON string
     - parameter string: Normal json string like '{"a":"b"}'
     
     - returns: The created JSON
     */
    public static func parse(string:String) -> JSON {
        return string.dataUsingEncoding(NSUTF8StringEncoding)
            .flatMap({JSON(data: $0)}) ?? JSON(NSNull())
    }
    
    /**
     Creates a JSON using the object.
     
     - parameter object:  The object must have the following properties: All objects are NSString/String, NSNumber/Int/Float/Double/Bool, NSArray/Array, NSDictionary/Dictionary, or NSNull; All dictionary keys are NSStrings/String; NSNumbers are not NaN or infinity.
     
     - returns: The created JSON
     */
    public init(_ object: AnyObject) {
        self.object = object
    }
    
    /**
     Creates a JSON from a [JSON]
     
     - parameter jsonArray: A Swift array of JSON objects
     
     - returns: The created JSON
     */
    public init(_ jsonArray:[JSON]) {
        self.init(jsonArray.map { $0.object })
    }
    
    /**
     Creates a JSON from a [String: JSON]
     
     - parameter jsonDictionary: A Swift dictionary of JSON objects
     
     - returns: The created JSON
     */
    public init(_ jsonDictionary:[String: JSON]) {
        var dictionary = [String: AnyObject]()
        for (key, json) in jsonDictionary {
            dictionary[key] = json.object
        }
        self.init(dictionary)
    }
    
    /// Private object
    private var rawArray: [AnyObject] = []
    private var rawDictionary: [String : AnyObject] = [:]
    private var rawString: String = ""
    private var rawNumber: NSNumber = 0
    private var rawNull: NSNull = NSNull()
    /// Private type
    private var _type: Type = .Null
    /// prviate error
    private var _error: NSError? = nil
    
    /// Object in JSON
    public var object: AnyObject {
        get {
            switch self.type {
            case .Array:
                return self.rawArray
            case .Dictionary:
                return self.rawDictionary
            case .String:
                return self.rawString
            case .Number:
                return self.rawNumber
            case .Bool:
                return self.rawNumber
            default:
                return self.rawNull
            }
        }
        set {
            _error = nil
            switch newValue {
            case let number as NSNumber:
                if number.isBool {
                    _type = .Bool
                } else {
                    _type = .Number
                }
                self.rawNumber = number
            case  let string as String:
                _type = .String
                self.rawString = string
            case  _ as NSNull:
                _type = .Null
            case let array as [AnyObject]:
                _type = .Array
                self.rawArray = array
            case let dictionary as [String : AnyObject]:
                _type = .Dictionary
                self.rawDictionary = dictionary
            default:
                _type = .Unknown
                _error = NSError(domain: ErrorDomain, code: ErrorUnsupportedType, userInfo: [NSLocalizedDescriptionKey: "It is a unsupported type"])
            }
        }
    }
    
    /// json type
    public var type: Type { get { return _type } }
    
    /// Error in JSON
    public var error: NSError? { get { return self._error } }
    
    /// The static null json
    @available(*, unavailable, renamed="null")
    public static var nullJSON: JSON { get { return null } }
    public static var null: JSON { get { return JSON(NSNull()) } }
}

// MARK: - CollectionType, SequenceType, Indexable
extension JSON : Swift.CollectionType, Swift.SequenceType, Swift.Indexable {
    
    public typealias Generator = JSONGenerator
    
    public typealias Index = JSONIndex
    
    public var startIndex: JSON.Index {
        switch self.type {
        case .Array:
            return JSONIndex(arrayIndex: self.rawArray.startIndex)
        case .Dictionary:
            return JSONIndex(dictionaryIndex: self.rawDictionary.startIndex)
        default:
            return JSONIndex()
        }
    }
    
    public var endIndex: JSON.Index {
        switch self.type {
        case .Array:
            return JSONIndex(arrayIndex: self.rawArray.endIndex)
        case .Dictionary:
            return JSONIndex(dictionaryIndex: self.rawDictionary.endIndex)
        default:
            return JSONIndex()
        }
    }
    
    public subscript (position: JSON.Index) -> JSON.Generator.Element {
        switch self.type {
        case .Array:
            return (String(position.arrayIndex), JSON(self.rawArray[position.arrayIndex!]))
        case .Dictionary:
            let (key, value) = self.rawDictionary[position.dictionaryIndex!]
            return (key, JSON(value))
        default:
            return ("", JSON.null)
        }
    }
    
    /// If `type` is `.Array` or `.Dictionary`, return `array.empty` or `dictonary.empty` otherwise return `true`.
    public var isEmpty: Bool {
        get {
            switch self.type {
            case .Array:
                return self.rawArray.isEmpty
            case .Dictionary:
                return self.rawDictionary.isEmpty
            default:
                return true
            }
        }
    }
    
    /// If `type` is `.Array` or `.Dictionary`, return `array.count` or `dictonary.count` otherwise return `0`.
    public var count: Int {
        switch self.type {
        case .Array:
            return self.rawArray.count
        case .Dictionary:
            return self.rawDictionary.count
        default:
            return 0
        }
    }
    
    public func underestimateCount() -> Int {
        switch self.type {
        case .Array:
            return self.rawArray.underestimateCount()
        case .Dictionary:
            return self.rawDictionary.underestimateCount()
        default:
            return 0
        }
    }
    
    /**
     If `type` is `.Array` or `.Dictionary`, return a generator over the elements like `Array` or `Dictionary`, otherwise return a generator over empty.
     
     - returns: Return a *generator* over the elements of JSON.
     */
    public func generate() -> JSON.Generator {
        return JSON.Generator(self)
    }
}

public struct JSONIndex: ForwardIndexType, _Incrementable, Equatable, Comparable {
    
    let arrayIndex: Int?
    let dictionaryIndex: DictionaryIndex<String, AnyObject>?
    
    let type: Type
    
    init(){
        self.arrayIndex = nil
        self.dictionaryIndex = nil
        self.type = .Unknown
    }
    
    init(arrayIndex: Int) {
        self.arrayIndex = arrayIndex
        self.dictionaryIndex = nil
        self.type = .Array
    }
    
    init(dictionaryIndex: DictionaryIndex<String, AnyObject>) {
        self.arrayIndex = nil
        self.dictionaryIndex = dictionaryIndex
        self.type = .Dictionary
    }
    
    public func successor() -> JSONIndex {
        switch self.type {
        case .Array:
            return JSONIndex(arrayIndex: self.arrayIndex!.successor())
        case .Dictionary:
            return JSONIndex(dictionaryIndex: self.dictionaryIndex!.successor())
        default:
            return JSONIndex()
        }
    }
}

public func ==(lhs: JSONIndex, rhs: JSONIndex) -> Bool {
    switch (lhs.type, rhs.type) {
    case (.Array, .Array):
        return lhs.arrayIndex == rhs.arrayIndex
    case (.Dictionary, .Dictionary):
        return lhs.dictionaryIndex == rhs.dictionaryIndex
    default:
        return false
    }
}

public func <(lhs: JSONIndex, rhs: JSONIndex) -> Bool {
    switch (lhs.type, rhs.type) {
    case (.Array, .Array):
        return lhs.arrayIndex < rhs.arrayIndex
    case (.Dictionary, .Dictionary):
        return lhs.dictionaryIndex < rhs.dictionaryIndex
    default:
        return false
    }
}

public func <=(lhs: JSONIndex, rhs: JSONIndex) -> Bool {
    switch (lhs.type, rhs.type) {
    case (.Array, .Array):
        return lhs.arrayIndex <= rhs.arrayIndex
    case (.Dictionary, .Dictionary):
        return lhs.dictionaryIndex <= rhs.dictionaryIndex
    default:
        return false
    }
}

public func >=(lhs: JSONIndex, rhs: JSONIndex) -> Bool {
    switch (lhs.type, rhs.type) {
    case (.Array, .Array):
        return lhs.arrayIndex >= rhs.arrayIndex
    case (.Dictionary, .Dictionary):
        return lhs.dictionaryIndex >= rhs.dictionaryIndex
    default:
        return false
    }
}

public func >(lhs: JSONIndex, rhs: JSONIndex) -> Bool {
    switch (lhs.type, rhs.type) {
    case (.Array, .Array):
        return lhs.arrayIndex > rhs.arrayIndex
    case (.Dictionary, .Dictionary):
        return lhs.dictionaryIndex > rhs.dictionaryIndex
    default:
        return false
    }
}

public struct JSONGenerator : GeneratorType {
    
    public typealias Element = (String, JSON)
    
    private let type: Type
    private var dictionayGenerate: DictionaryGenerator<String, AnyObject>?
    private var arrayGenerate: IndexingGenerator<[AnyObject]>?
    private var arrayIndex: Int = 0
    
    init(_ json: JSON) {
        self.type = json.type
        if type == .Array {
            self.arrayGenerate = json.rawArray.generate()
        }else {
            self.dictionayGenerate = json.rawDictionary.generate()
        }
    }
    
    public mutating func next() -> JSONGenerator.Element? {
        switch self.type {
        case .Array:
            if let o = self.arrayGenerate?.next() {
                return (String(self.arrayIndex++), JSON(o))
            } else {
                return nil
            }
        case .Dictionary:
            if let (k, v): (String, AnyObject) = self.dictionayGenerate?.next() {
                return (k, JSON(v))
            } else {
                return nil
            }
        default:
            return nil
        }
    }
}

// MARK: - Subscript

/**
*  To mark both String and Int can be used in subscript.
*/
public enum JSONKey {
    case Index(Int)
    case Key(String)
}

public protocol JSONSubscriptType {
    var jsonKey:JSONKey { get }
}

extension Int: JSONSubscriptType {
    public var jsonKey:JSONKey {
        return JSONKey.Index(self)
    }
}

extension String: JSONSubscriptType {
    public var jsonKey:JSONKey {
        return JSONKey.Key(self)
    }
}

extension JSON {
    
    /// If `type` is `.Array`, return json which's object is `array[index]`, otherwise return null json with error.
    private subscript(index index: Int) -> JSON {
        get {
            if self.type != .Array {
                var r = JSON.null
                r._error = self._error ?? NSError(domain: ErrorDomain, code: ErrorWrongType, userInfo: [NSLocalizedDescriptionKey: "Array[\(index)] failure, It is not an array"])
                return r
            } else if index >= 0 && index < self.rawArray.count {
                return JSON(self.rawArray[index])
            } else {
                var r = JSON.null
                r._error = NSError(domain: ErrorDomain, code:ErrorIndexOutOfBounds , userInfo: [NSLocalizedDescriptionKey: "Array[\(index)] is out of bounds"])
                return r
            }
        }
        set {
            if self.type == .Array {
                if self.rawArray.count > index && newValue.error == nil {
                    self.rawArray[index] = newValue.object
                }
            }
        }
    }
    
    /// If `type` is `.Dictionary`, return json which's object is `dictionary[key]` , otherwise return null json with error.
    private subscript(key key: String) -> JSON {
        get {
            var r = JSON.null
            if self.type == .Dictionary {
                if let o = self.rawDictionary[key] {
                    r = JSON(o)
                } else {
                    r._error = NSError(domain: ErrorDomain, code: ErrorNotExist, userInfo: [NSLocalizedDescriptionKey: "Dictionary[\"\(key)\"] does not exist"])
                }
            } else {
                r._error = self._error ?? NSError(domain: ErrorDomain, code: ErrorWrongType, userInfo: [NSLocalizedDescriptionKey: "Dictionary[\"\(key)\"] failure, It is not an dictionary"])
            }
            return r
        }
        set {
            if self.type == .Dictionary && newValue.error == nil {
                self.rawDictionary[key] = newValue.object
            }
        }
    }
    
    /// If `sub` is `Int`, return `subscript(index:)`; If `sub` is `String`,  return `subscript(key:)`.
    private subscript(sub sub: JSONSubscriptType) -> JSON {
        get {
            switch sub.jsonKey {
            case .Index(let index): return self[index: index]
            case .Key(let key): return self[key: key]
            }
        }
        set {
            switch sub.jsonKey {
            case .Index(let index): self[index: index] = newValue
            case .Key(let key): self[key: key] = newValue
            }
        }
    }
    
    /**
     Find a json in the complex data structuresby using the Int/String's array.
     
     - parameter path: The target json's path. Example:
     
     let json = JSON[data]
     let path = [9,"list","person","name"]
     let name = json[path]
     
     The same as: let name = json[9]["list"]["person"]["name"]
     
     - returns: Return a json found by the path or a null json with error
     */
    public subscript(path: [JSONSubscriptType]) -> JSON {
        get {
            return path.reduce(self) { $0[sub: $1] }
        }
        set {
            switch path.count {
            case 0:
                return
            case 1:
                self[sub:path[0]].object = newValue.object
            default:
                var aPath = path; aPath.removeAtIndex(0)
                var nextJSON = self[sub: path[0]]
                nextJSON[aPath] = newValue
                self[sub: path[0]] = nextJSON
            }
        }
    }
    
    /**
     Find a json in the complex data structuresby using the Int/String's array.
     
     - parameter path: The target json's path. Example:
     
     let name = json[9,"list","person","name"]
     
     The same as: let name = json[9]["list"]["person"]["name"]
     
     - returns: Return a json found by the path or a null json with error
     */
    public subscript(path: JSONSubscriptType...) -> JSON {
        get {
            return self[path]
        }
        set {
            self[path] = newValue
        }
    }
}

// MARK: - LiteralConvertible

extension JSON: Swift.StringLiteralConvertible {
    
    public init(stringLiteral value: StringLiteralType) {
        self.init(value)
    }
    
    public init(extendedGraphemeClusterLiteral value: StringLiteralType) {
        self.init(value)
    }
    
    public init(unicodeScalarLiteral value: StringLiteralType) {
        self.init(value)
    }
}

extension JSON: Swift.IntegerLiteralConvertible {
    
    public init(integerLiteral value: IntegerLiteralType) {
        self.init(value)
    }
}

extension JSON: Swift.BooleanLiteralConvertible {
    
    public init(booleanLiteral value: BooleanLiteralType) {
        self.init(value)
    }
}

extension JSON: Swift.FloatLiteralConvertible {
    
    public init(floatLiteral value: FloatLiteralType) {
        self.init(value)
    }
}

extension JSON: Swift.DictionaryLiteralConvertible {
    
    public init(dictionaryLiteral elements: (String, AnyObject)...) {
        self.init(elements.reduce([String : AnyObject]()){(dictionary: [String : AnyObject], element:(String, AnyObject)) -> [String : AnyObject] in
            var d = dictionary
            d[element.0] = element.1
            return d
            })
    }
}

extension JSON: Swift.ArrayLiteralConvertible {
    
    public init(arrayLiteral elements: AnyObject...) {
        self.init(elements)
    }
}

extension JSON: Swift.NilLiteralConvertible {
    
    public init(nilLiteral: ()) {
        self.init(NSNull())
    }
}

// MARK: - Raw

extension JSON: Swift.RawRepresentable {
    
    public init?(rawValue: AnyObject) {
        if JSON(rawValue).type == .Unknown {
            return nil
        } else {
            self.init(rawValue)
        }
    }
    
    public var rawValue: AnyObject {
        return self.object
    }
    
    public func rawData(options opt: NSJSONWritingOptions = NSJSONWritingOptions(rawValue: 0)) throws -> NSData {
        guard NSJSONSerialization.isValidJSONObject(self.object) else {
            throw NSError(domain: ErrorDomain, code: ErrorInvalidJSON, userInfo: [NSLocalizedDescriptionKey: "JSON is invalid"])
        }
        
        return try NSJSONSerialization.dataWithJSONObject(self.object, options: opt)
    }
    
    public func rawString(encoding: UInt = NSUTF8StringEncoding, options opt: NSJSONWritingOptions = .PrettyPrinted) -> String? {
        switch self.type {
        case .Array, .Dictionary:
            do {
                let data = try self.rawData(options: opt)
                return NSString(data: data, encoding: encoding) as? String
            } catch _ {
                return nil
            }
        case .String:
            return self.rawString
        case .Number:
            return self.rawNumber.stringValue
        case .Bool:
            return self.rawNumber.boolValue.description
        case .Null:
            return "null"
        default:
            return nil
        }
    }
}

// MARK: - Printable, DebugPrintable

extension JSON: Swift.Printable, Swift.DebugPrintable {
    
    public var description: String {
        if let string = self.rawString(options:.PrettyPrinted) {
            return string
        } else {
            return "unknown"
        }
    }
    
    public var debugDescription: String {
        return description
    }
}

// MARK: - Array

extension JSON {
    
    //Optional [JSON]
    public var array: [JSON]? {
        get {
            if self.type == .Array {
                return self.rawArray.map{ JSON($0) }
            } else {
                return nil
            }
        }
    }
    
    //Non-optional [JSON]
    public var arrayValue: [JSON] {
        get {
            return self.array ?? []
        }
    }
    
    //Optional [AnyObject]
    public var arrayObject: [AnyObject]? {
        get {
            switch self.type {
            case .Array:
                return self.rawArray
            default:
                return nil
            }
        }
        set {
            if let array = newValue {
                self.object = array
            } else {
                self.object = NSNull()
            }
        }
    }
}

// MARK: - Dictionary

extension JSON {
    
    //Optional [String : JSON]
    public var dictionary: [String : JSON]? {
        if self.type == .Dictionary {
            return self.rawDictionary.reduce([String : JSON]()) { (dictionary: [String : JSON], element: (String, AnyObject)) -> [String : JSON] in
                var d = dictionary
                d[element.0] = JSON(element.1)
                return d
            }
        } else {
            return nil
        }
    }
    
    //Non-optional [String : JSON]
    public var dictionaryValue: [String : JSON] {
        return self.dictionary ?? [:]
    }
    
    //Optional [String : AnyObject]
    public var dictionaryObject: [String : AnyObject]? {
        get {
            switch self.type {
            case .Dictionary:
                return self.rawDictionary
            default:
                return nil
            }
        }
        set {
            if let v = newValue {
                self.object = v
            } else {
                self.object = NSNull()
            }
        }
    }
}

// MARK: - Bool

extension JSON: Swift.BooleanType {
    
    //Optional bool
    public var bool: Bool? {
        get {
            switch self.type {
            case .Bool:
                return self.rawNumber.boolValue
            default:
                return nil
            }
        }
        set {
            if let newValue = newValue {
                self.object = NSNumber(bool: newValue)
            } else {
                self.object = NSNull()
            }
        }
    }
    
    //Non-optional bool
    public var boolValue: Bool {
        get {
            switch self.type {
            case .Bool, .Number, .String:
                return self.object.boolValue
            default:
                return false
            }
        }
        set {
            self.object = NSNumber(bool: newValue)
        }
    }
}

// MARK: - String

extension JSON {
    
    //Optional string
    public var string: String? {
        get {
            switch self.type {
            case .String:
                return self.object as? String
            default:
                return nil
            }
        }
        set {
            if let newValue = newValue {
                self.object = NSString(string:newValue)
            } else {
                self.object = NSNull()
            }
        }
    }
    
    //Non-optional string
    public var stringValue: String {
        get {
            switch self.type {
            case .String:
                return self.object as? String ?? ""
            case .Number:
                return self.object.stringValue
            case .Bool:
                return (self.object as? Bool).map { String($0) } ?? ""
            default:
                return ""
            }
        }
        set {
            self.object = NSString(string:newValue)
        }
    }
}

// MARK: - Number
extension JSON {
    
    //Optional number
    public var number: NSNumber? {
        get {
            switch self.type {
            case .Number, .Bool:
                return self.rawNumber
            default:
                return nil
            }
        }
        set {
            self.object = newValue ?? NSNull()
        }
    }
    
    //Non-optional number
    public var numberValue: NSNumber {
        get {
            switch self.type {
            case .String:
                let decimal = NSDecimalNumber(string: self.object as? String)
                if decimal == NSDecimalNumber.notANumber() {  // indicates parse error
                    return NSDecimalNumber.zero()
                }
                return decimal
            case .Number, .Bool:
                return self.object as? NSNumber ?? NSNumber(int: 0)
            default:
                return NSNumber(double: 0.0)
            }
        }
        set {
            self.object = newValue
        }
    }
}

//MARK: - Null
extension JSON {
    
    public var null: NSNull? {
        get {
            switch self.type {
            case .Null:
                return self.rawNull
            default:
                return nil
            }
        }
        set {
            self.object = NSNull()
        }
    }
    public func isExists() -> Bool{
        if let errorValue = error where errorValue.code == ErrorNotExist{
            return false
        }
        return true
    }
}

//MARK: - URL
extension JSON {
    
    //Optional URL
    public var URL: NSURL? {
        get {
            switch self.type {
            case .String:
                if let encodedString_ = self.rawString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()) {
                    return NSURL(string: encodedString_)
                } else {
                    return nil
                }
            default:
                return nil
            }
        }
        set {
            self.object = newValue?.absoluteString ?? NSNull()
        }
    }
}

// MARK: - Int, Double, Float, Int8, Int16, Int32, Int64

extension JSON {
    
    public var double: Double? {
        get {
            return self.number?.doubleValue
        }
        set {
            if let newValue = newValue {
                self.object = NSNumber(double: newValue)
            } else {
                self.object = NSNull()
            }
        }
    }
    
    public var doubleValue: Double {
        get {
            return self.numberValue.doubleValue
        }
        set {
            self.object = NSNumber(double: newValue)
        }
    }
    
    public var float: Float? {
        get {
            return self.number?.floatValue
        }
        set {
            if let newValue = newValue {
                self.object = NSNumber(float: newValue)
            } else {
                self.object = NSNull()
            }
        }
    }
    
    public var floatValue: Float {
        get {
            return self.numberValue.floatValue
        }
        set {
            self.object = NSNumber(float: newValue)
        }
    }
    
    public var int: Int? {
        get {
            return self.number?.longValue
        }
        set {
            if let newValue = newValue {
                self.object = NSNumber(integer: newValue)
            } else {
                self.object = NSNull()
            }
        }
    }
    
    public var intValue: Int {
        get {
            return self.numberValue.integerValue
        }
        set {
            self.object = NSNumber(integer: newValue)
        }
    }
    
    public var uInt: UInt? {
        get {
            return self.number?.unsignedLongValue
        }
        set {
            if let newValue = newValue {
                self.object = NSNumber(unsignedLong: newValue)
            } else {
                self.object = NSNull()
            }
        }
    }
    
    public var uIntValue: UInt {
        get {
            return self.numberValue.unsignedLongValue
        }
        set {
            self.object = NSNumber(unsignedLong: newValue)
        }
    }
    
    public var int8: Int8? {
        get {
            return self.number?.charValue
        }
        set {
            if let newValue = newValue {
                self.object = NSNumber(char: newValue)
            } else {
                self.object =  NSNull()
            }
        }
    }
    
    public var int8Value: Int8 {
        get {
            return self.numberValue.charValue
        }
        set {
            self.object = NSNumber(char: newValue)
        }
    }
    
    public var uInt8: UInt8? {
        get {
            return self.number?.unsignedCharValue
        }
        set {
            if let newValue = newValue {
                self.object = NSNumber(unsignedChar: newValue)
            } else {
                self.object =  NSNull()
            }
        }
    }
    
    public var uInt8Value: UInt8 {
        get {
            return self.numberValue.unsignedCharValue
        }
        set {
            self.object = NSNumber(unsignedChar: newValue)
        }
    }
    
    public var int16: Int16? {
        get {
            return self.number?.shortValue
        }
        set {
            if let newValue = newValue {
                self.object = NSNumber(short: newValue)
            } else {
                self.object =  NSNull()
            }
        }
    }
    
    public var int16Value: Int16 {
        get {
            return self.numberValue.shortValue
        }
        set {
            self.object = NSNumber(short: newValue)
        }
    }
    
    public var uInt16: UInt16? {
        get {
            return self.number?.unsignedShortValue
        }
        set {
            if let newValue = newValue {
                self.object = NSNumber(unsignedShort: newValue)
            } else {
                self.object =  NSNull()
            }
        }
    }
    
    public var uInt16Value: UInt16 {
        get {
            return self.numberValue.unsignedShortValue
        }
        set {
            self.object = NSNumber(unsignedShort: newValue)
        }
    }
    
    public var int32: Int32? {
        get {
            return self.number?.intValue
        }
        set {
            if let newValue = newValue {
                self.object = NSNumber(int: newValue)
            } else {
                self.object =  NSNull()
            }
        }
    }
    
    public var int32Value: Int32 {
        get {
            return self.numberValue.intValue
        }
        set {
            self.object = NSNumber(int: newValue)
        }
    }
    
    public var uInt32: UInt32? {
        get {
            return self.number?.unsignedIntValue
        }
        set {
            if let newValue = newValue {
                self.object = NSNumber(unsignedInt: newValue)
            } else {
                self.object =  NSNull()
            }
        }
    }
    
    public var uInt32Value: UInt32 {
        get {
            return self.numberValue.unsignedIntValue
        }
        set {
            self.object = NSNumber(unsignedInt: newValue)
        }
    }
    
    public var int64: Int64? {
        get {
            return self.number?.longLongValue
        }
        set {
            if let newValue = newValue {
                self.object = NSNumber(longLong: newValue)
            } else {
                self.object =  NSNull()
            }
        }
    }
    
    public var int64Value: Int64 {
        get {
            return self.numberValue.longLongValue
        }
        set {
            self.object = NSNumber(longLong: newValue)
        }
    }
    
    public var uInt64: UInt64? {
        get {
            return self.number?.unsignedLongLongValue
        }
        set {
            if let newValue = newValue {
                self.object = NSNumber(unsignedLongLong: newValue)
            } else {
                self.object =  NSNull()
            }
        }
    }
    
    public var uInt64Value: UInt64 {
        get {
            return self.numberValue.unsignedLongLongValue
        }
        set {
            self.object = NSNumber(unsignedLongLong: newValue)
        }
    }
}

//MARK: - Comparable
extension JSON : Swift.Comparable {}

public func ==(lhs: JSON, rhs: JSON) -> Bool {
    
    switch (lhs.type, rhs.type) {
    case (.Number, .Number):
        return lhs.rawNumber == rhs.rawNumber
    case (.String, .String):
        return lhs.rawString == rhs.rawString
    case (.Bool, .Bool):
        return lhs.rawNumber.boolValue == rhs.rawNumber.boolValue
    case (.Array, .Array):
        return lhs.rawArray as NSArray == rhs.rawArray as NSArray
    case (.Dictionary, .Dictionary):
        return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary
    case (.Null, .Null):
        return true
    default:
        return false
    }
}

public func <=(lhs: JSON, rhs: JSON) -> Bool {
    
    switch (lhs.type, rhs.type) {
    case (.Number, .Number):
        return lhs.rawNumber <= rhs.rawNumber
    case (.String, .String):
        return lhs.rawString <= rhs.rawString
    case (.Bool, .Bool):
        return lhs.rawNumber.boolValue == rhs.rawNumber.boolValue
    case (.Array, .Array):
        return lhs.rawArray as NSArray == rhs.rawArray as NSArray
    case (.Dictionary, .Dictionary):
        return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary
    case (.Null, .Null):
        return true
    default:
        return false
    }
}

public func >=(lhs: JSON, rhs: JSON) -> Bool {
    
    switch (lhs.type, rhs.type) {
    case (.Number, .Number):
        return lhs.rawNumber >= rhs.rawNumber
    case (.String, .String):
        return lhs.rawString >= rhs.rawString
    case (.Bool, .Bool):
        return lhs.rawNumber.boolValue == rhs.rawNumber.boolValue
    case (.Array, .Array):
        return lhs.rawArray as NSArray == rhs.rawArray as NSArray
    case (.Dictionary, .Dictionary):
        return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary
    case (.Null, .Null):
        return true
    default:
        return false
    }
}

public func >(lhs: JSON, rhs: JSON) -> Bool {
    
    switch (lhs.type, rhs.type) {
    case (.Number, .Number):
        return lhs.rawNumber > rhs.rawNumber
    case (.String, .String):
        return lhs.rawString > rhs.rawString
    default:
        return false
    }
}

public func <(lhs: JSON, rhs: JSON) -> Bool {
    
    switch (lhs.type, rhs.type) {
    case (.Number, .Number):
        return lhs.rawNumber < rhs.rawNumber
    case (.String, .String):
        return lhs.rawString < rhs.rawString
    default:
        return false
    }
}

private let trueNumber = NSNumber(bool: true)
private let falseNumber = NSNumber(bool: false)
private let trueObjCType = String.fromCString(trueNumber.objCType)
private let falseObjCType = String.fromCString(falseNumber.objCType)

// MARK: - NSNumber: Comparable

extension NSNumber {
    var isBool:Bool {
        get {
            let objCType = String.fromCString(self.objCType)
            if (self.compare(trueNumber) == NSComparisonResult.OrderedSame && objCType == trueObjCType)
                || (self.compare(falseNumber) == NSComparisonResult.OrderedSame && objCType == falseObjCType){
                    return true
            } else {
                return false
            }
        }
    }
}

func ==(lhs: NSNumber, rhs: NSNumber) -> Bool {
    switch (lhs.isBool, rhs.isBool) {
    case (false, true):
        return false
    case (true, false):
        return false
    default:
        return lhs.compare(rhs) == NSComparisonResult.OrderedSame
    }
}

func !=(lhs: NSNumber, rhs: NSNumber) -> Bool {
    return !(lhs == rhs)
}

func <(lhs: NSNumber, rhs: NSNumber) -> Bool {
    
    switch (lhs.isBool, rhs.isBool) {
    case (false, true):
        return false
    case (true, false):
        return false
    default:
        return lhs.compare(rhs) == NSComparisonResult.OrderedAscending
    }
}

func >(lhs: NSNumber, rhs: NSNumber) -> Bool {
    
    switch (lhs.isBool, rhs.isBool) {
    case (false, true):
        return false
    case (true, false):
        return false
    default:
        return lhs.compare(rhs) == NSComparisonResult.OrderedDescending
    }
}

func <=(lhs: NSNumber, rhs: NSNumber) -> Bool {
    
    switch (lhs.isBool, rhs.isBool) {
    case (false, true):
        return false
    case (true, false):
        return false
    default:
        return lhs.compare(rhs) != NSComparisonResult.OrderedDescending
    }
}

func >=(lhs: NSNumber, rhs: NSNumber) -> Bool {
    
    switch (lhs.isBool, rhs.isBool) {
    case (false, true):
        return false
    case (true, false):
        return false
    default:
        return lhs.compare(rhs) != NSComparisonResult.OrderedAscending
    }
}

================================================
FILE: NimbusMenuBarApp/Nimbus/Nimbus/main.swift
================================================
//
//  main.swift
//  Nimbus
//
//  Created by Ethan Lowman on 7/25/14.
//  Copyright (c) 2014 Ethanal. All rights reserved.
//

import Cocoa

NSApplicationMain(Process.argc, Process.unsafeArgv)

================================================
FILE: NimbusMenuBarApp/Nimbus/Nimbus.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
	archiveVersion = 1;
	classes = {
	};
	objectVersion = 46;
	objects = {

/* Begin PBXBuildFile section */
		AA0C67691983375C004BBF89 /* menubar-error.png in Resources */ = {isa = PBXBuildFile; fileRef = AA0C675B1983375C004BBF89 /* menubar-error.png */; };
		AA0C676A1983375C004BBF89 /* menubar-error@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA0C675C1983375C004BBF89 /* menubar-error@2x.png */; };
		AA0C676B1983375C004BBF89 /* menubar-highlighted.png in Resources */ = {isa = PBXBuildFile; fileRef = AA0C675D1983375C004BBF89 /* menubar-highlighted.png */; };
		AA0C676C1983375C004BBF89 /* menubar-highlighted@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA0C675E1983375C004BBF89 /* menubar-highlighted@2x.png */; };
		AA0C676D1983375C004BBF89 /* menubar-progress-1.png in Resources */ = {isa = PBXBuildFile; fileRef = AA0C675F1983375C004BBF89 /* menubar-progress-1.png */; };
		AA0C676E1983375C004BBF89 /* menubar-progress-1@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA0C67601983375C004BBF89 /* menubar-progress-1@2x.png */; };
		AA0C676F1983375C004BBF89 /* menubar-progress-2.png in Resources */ = {isa = PBXBuildFile; fileRef = AA0C67611983375C004BBF89 /* menubar-progress-2.png */; };
		AA0C67701983375C004BBF89 /* menubar-progress-2@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA0C67621983375C004BBF89 /* menubar-progress-2@2x.png */; };
		AA0C67711983375C004BBF89 /* menubar-progress-3.png in Resources */ = {isa = PBXBuildFile; fileRef = AA0C67631983375C004BBF89 /* menubar-progress-3.png */; };
		AA0C67721983375C004BBF89 /* menubar-progress-3@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA0C67641983375C004BBF89 /* menubar-progress-3@2x.png */; };
		AA0C67731983375C004BBF89 /* menubar-success.png in Resources */ = {isa = PBXBuildFile; fileRef = AA0C67651983375C004BBF89 /* menubar-success.png */; };
		AA0C67741983375C004BBF89 /* menubar-success@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA0C67661983375C004BBF89 /* menubar-success@2x.png */; };
		AA0C67751983375C004BBF89 /* menubar.png in Resources */ = {isa = PBXBuildFile; fileRef = AA0C67671983375C004BBF89 /* menubar.png */; };
		AA0C67761983375C004BBF89 /* menubar@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AA0C67681983375C004BBF89 /* menubar@2x.png */; };
		AA0C678619835367004BBF89 /* APIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0C678519835367004BBF89 /* APIClient.swift */; };
		AA9325B319831B3E0027847E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA9325B219831B3E0027847E /* main.swift */; };
		AA9325B519831B3E0027847E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA9325B419831B3E0027847E /* AppDelegate.swift */; };
		AA9325B719831B3E0027847E /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AA9325B619831B3E0027847E /* Images.xcassets */; };
		AA9325BA19831B3E0027847E /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = AA9325B819831B3E0027847E /* MainMenu.xib */; };
		AA9325C619831B3E0027847E /* NimbusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA9325C519831B3E0027847E /* NimbusTests.swift */; };
		AA9325F119831C860027847E /* StatusItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA9325F019831C860027847E /* StatusItemView.swift */; };
		AAD4434A19835E9900AA5459 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD4434819835E9900AA5459 /* PreferencesWindowController.swift */; };
		AAD4434B19835E9900AA5459 /* PreferencesWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = AAD4434919835E9900AA5459 /* PreferencesWindowController.xib */; };
		AAD4434D19835ED800AA5459 /* PreferencesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD4434C19835ED800AA5459 /* PreferencesManager.swift */; };
		AAD4434F1983952100AA5459 /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD4434E1983952100AA5459 /* KeychainManager.swift */; };
		AAD443511984085B00AA5459 /* SwiftyJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD443501984085B00AA5459 /* SwiftyJSON.swift */; };
		AADCA9EE1984DFEB00304703 /* ScreenshotWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = AADCA9ED1984DFEB00304703 /* ScreenshotWatcher.m */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
		AA9325C019831B3E0027847E /* PBXContainerItemProxy */ = {
			isa = PBXContainerItemProxy;
			containerPortal = AA9325A519831B3E0027847E /* Project object */;
			proxyType = 1;
			remoteGlobalIDString = AA9325AC19831B3E0027847E;
			remoteInfo = Nimbus;
		};
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
		AA0C675B1983375C004BBF89 /* menubar-error.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menubar-error.png"; sourceTree = "<group>"; };
		AA0C675C1983375C004BBF89 /* menubar-error@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menubar-error@2x.png"; sourceTree = "<group>"; };
		AA0C675D1983375C004BBF89 /* menubar-highlighted.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menubar-highlighted.png"; sourceTree = "<group>"; };
		AA0C675E1983375C004BBF89 /* menubar-highlighted@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menubar-highlighted@2x.png"; sourceTree = "<group>"; };
		AA0C675F1983375C004BBF89 /* menubar-progress-1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menubar-progress-1.png"; sourceTree = "<group>"; };
		AA0C67601983375C004BBF89 /* menubar-progress-1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menubar-progress-1@2x.png"; sourceTree = "<group>"; };
		AA0C67611983375C004BBF89 /* menubar-progress-2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menubar-progress-2.png"; sourceTree = "<group>"; };
		AA0C67621983375C004BBF89 /* menubar-progress-2@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menubar-progress-2@2x.png"; sourceTree = "<group>"; };
		AA0C67631983375C004BBF89 /* menubar-progress-3.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menubar-progress-3.png"; sourceTree = "<group>"; };
		AA0C67641983375C004BBF89 /* menubar-progress-3@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menubar-progress-3@2x.png"; sourceTree = "<group>"; };
		AA0C67651983375C004BBF89 /* menubar-success.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menubar-success.png"; sourceTree = "<group>"; };
		AA0C67661983375C004BBF89 /* menubar-success@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menubar-success@2x.png"; sourceTree = "<group>"; };
		AA0C67671983375C004BBF89 /* menubar.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menubar.png; sourceTree = "<group>"; };
		AA0C67681983375C004BBF89 /* menubar@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menubar@2x.png"; sourceTree = "<group>"; };
		AA0C678519835367004BBF89 /* APIClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIClient.swift; sourceTree = "<group>"; };
		AA9325AD19831B3E0027847E /* Nimbus.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Nimbus.app; sourceTree = BUILT_PRODUCTS_DIR; };
		AA9325B119831B3E0027847E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
		AA9325B219831B3E0027847E /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
		AA9325B419831B3E0027847E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
		AA9325B619831B3E0027847E /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
		AA9325B919831B3E0027847E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
		AA9325BF19831B3E0027847E /* NimbusTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NimbusTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
		AA9325C419831B3E0027847E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
		AA9325C519831B3E0027847E /* NimbusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NimbusTests.swift; sourceTree = "<group>"; };
		AA9325F019831C860027847E /* StatusItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusItemView.swift; sourceTree = "<group>"; };
		AAD4434819835E9900AA5459 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; };
		AAD4434919835E9900AA5459 /* PreferencesWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PreferencesWindowController.xib; sourceTree = "<group>"; };
		AAD4434C19835ED800AA5459 /* PreferencesManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesManager.swift; sourceTree = "<group>"; };
		AAD4434E1983952100AA5459 /* KeychainManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainManager.swift; sourceTree = "<group>"; };
		AAD443501984085B00AA5459 /* SwiftyJSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyJSON.swift; sourceTree = "<group>"; };
		AADCA9E81984DCC400304703 /* Nimbus-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Nimbus-Bridging-Header.h"; sourceTree = "<group>"; };
		AADCA9EC1984DFEB00304703 /* ScreenshotWatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScreenshotWatcher.h; sourceTree = "<group>"; };
		AADCA9ED1984DFEB00304703 /* ScreenshotWatcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ScreenshotWatcher.m; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
		AA9325AA19831B3E0027847E /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		AA9325BC19831B3E0027847E /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
		AA9325A419831B3E0027847E = {
			isa = PBXGroup;
			children = (
				AA9325AF19831B3E0027847E /* Nimbus */,
				AA9325C219831B3E0027847E /* NimbusTests */,
				AA9325AE19831B3E0027847E /* Products */,
			);
			sourceTree = "<group>";
		};
		AA9325AE19831B3E0027847E /* Products */ = {
			isa = PBXGroup;
			children = (
				AA9325AD19831B3E0027847E /* Nimbus.app */,
				AA9325BF19831B3E0027847E /* NimbusTests.xctest */,
			);
			name = Products;
			sourceTree = "<group>";
		};
		AA9325AF19831B3E0027847E /* Nimbus */ = {
			isa = PBXGroup;
			children = (
				AA9325B419831B3E0027847E /* AppDelegate.swift */,
				AA9325F019831C860027847E /* StatusItemView.swift */,
				AAD4434819835E9900AA5459 /* PreferencesWindowController.swift */,
				AAD4434919835E9900AA5459 /* PreferencesWindowController.xib */,
				AA0C678519835367004BBF89 /* APIClient.swift */,
				AAD4434C19835ED800AA5459 /* PreferencesManager.swift */,
				AAD4434E1983952100AA5459 /* KeychainManager.swift */,
				AAD443501984085B00AA5459 /* SwiftyJSON.swift */,
				AADCA9EC1984DFEB00304703 /* ScreenshotWatcher.h */,
				AADCA9ED1984DFEB00304703 /* ScreenshotWatcher.m */,
				AADCA9E81984DCC400304703 /* Nimbus-Bridging-Header.h */,
				AA9325B619831B3E0027847E /* Images.xcassets */,
				AA9325CF19831B6A0027847E /* Assets */,
				AA9325B019831B3E0027847E /* Supporting Files */,
			);
			path = Nimbus;
			sourceTree = "<group>";
		};
		AA9325B019831B3E0027847E /* Supporting Files */ = {
			isa = PBXGroup;
			children = (
				AA9325B119831B3E0027847E /* Info.plist */,
				AA9325B819831B3E0027847E /* MainMenu.xib */,
				AA9325B219831B3E0027847E /* main.swift */,
			);
			name = "Supporting Files";
			sourceTree = "<group>";
		};
		AA9325C219831B3E0027847E /* NimbusTests */ = {
			isa = PBXGroup;
			children = (
				AA9325C519831B3E0027847E /* NimbusTests.swift */,
				AA9325C319831B3E0027847E /* Supporting Files */,
			);
			path = NimbusTests;
			sourceTree = "<group>";
		};
		AA9325C319831B3E0027847E /* Supporting Files */ = {
			isa = PBXGroup;
			children = (
				AA9325C419831B3E0027847E /* Info.plist */,
			);
			name = "Supporting Files";
			sourceTree = "<group>";
		};
		AA9325CF19831B6A0027847E /* Assets */ = {
			isa = PBXGroup;
			children = (
				AA0C675B1983375C004BBF89 /* menubar-error.png */,
				AA0C675C1983375C004BBF89 /* menubar-error@2x.png */,
				AA0C675D1983375C004BBF89 /* menubar-highlighted.png */,
				AA0C675E1983375C004BBF89 /* menubar-highlighted@2x.png */,
				AA0C675F1983375C004BBF89 /* menubar-progress-1.png */,
				AA0C67601983375C004BBF89 /* menubar-progress-1@2x.png */,
				AA0C67611983375C004BBF89 /* menubar-progress-2.png */,
				AA0C67621983375C004BBF89 /* menubar-progress-2@2x.png */,
				AA0C67631983375C004BBF89 /* menubar-progress-3.png */,
				AA0C67641983375C004BBF89 /* menubar-progress-3@2x.png */,
				AA0C67651983375C004BBF89 /* menubar-success.png */,
				AA0C67661983375C004BBF89 /* menubar-success@2x.png */,
				AA0C67671983375C004BBF89 /* menubar.png */,
				AA0C67681983375C004BBF89 /* menubar@2x.png */,
			);
			name = Assets;
			sourceTree = "<group>";
		};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
		AA9325AC19831B3E0027847E /* Nimbus */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = AA9325C919831B3E0027847E /* Build configuration list for PBXNativeTarget "Nimbus" */;
			buildPhases = (
				AA9325A919831B3E0027847E /* Sources */,
				AA9325AA19831B3E0027847E /* Frameworks */,
				AA9325AB19831B3E0027847E /* Resources */,
			);
			buildRules = (
			);
			dependencies = (
			);
			name = Nimbus;
			productName = Nimbus;
			productReference = AA9325AD19831B3E0027847E /* Nimbus.app */;
			productType = "com.apple.product-type.application";
		};
		AA9325BE19831B3E0027847E /* NimbusTests */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = AA9325CC19831B3E0027847E /* Build configuration list for PBXNativeTarget "NimbusTests" */;
			buildPhases = (
				AA9325BB19831B3E0027847E /* Sources */,
				AA9325BC19831B3E0027847E /* Frameworks */,
				AA9325BD19831B3E0027847E /* Resources */,
			);
			buildRules = (
			);
			dependencies = (
				AA9325C119831B3E0027847E /* PBXTargetDependency */,
			);
			name = NimbusTests;
			productName = NimbusTests;
			productReference = AA9325BF19831B3E0027847E /* NimbusTests.xctest */;
			productType = "com.apple.product-type.bundle.unit-test";
		};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
		AA9325A519831B3E0027847E /* Project object */ = {
			isa = PBXProject;
			attributes = {
				LastSwiftMigration = 0720;
				LastSwiftUpdateCheck = 0720;
				LastUpgradeCheck = 0720;
				ORGANIZATIONNAME = Ethanal;
				TargetAttributes = {
					AA9325AC19831B3E0027847E = {
						CreatedOnToolsVersion = 6.0;
					};
					AA9325BE19831B3E0027847E = {
						CreatedOnToolsVersion = 6.0;
						TestTargetID = AA9325AC19831B3E0027847E;
					};
				};
			};
			buildConfigurationList = AA9325A819831B3E0027847E /* Build configuration list for PBXProject "Nimbus" */;
			compatibilityVersion = "Xcode 3.2";
			developmentRegion = English;
			hasScannedForEncodings = 0;
			knownRegions = (
				en,
				Base,
			);
			mainGroup = AA9325A419831B3E0027847E;
			productRefGroup = AA9325AE19831B3E0027847E /* Products */;
			projectDirPath = "";
			projectRoot = "";
			targets = (
				AA9325AC19831B3E0027847E /* Nimbus */,
				AA9325BE19831B3E0027847E /* NimbusTests */,
			);
		};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
		AA9325AB19831B3E0027847E /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				AAD4434B19835E9900AA5459 /* PreferencesWindowController.xib in Resources */,
				AA0C676F1983375C004BBF89 /* menubar-progress-2.png in Resources */,
				AA0C67711983375C004BBF89 /* menubar-progress-3.png in Resources */,
				AA0C67701983375C004BBF89 /* menubar-progress-2@2x.png in Resources */,
				AA9325B719831B3E0027847E /* Images.xcassets in Resources */,
				AA0C67691983375C004BBF89 /* menubar-error.png in Resources */,
				AA0C676E1983375C004BBF89 /* menubar-progress-1@2x.png in Resources */,
				AA0C67761983375C004BBF89 /* menubar@2x.png in Resources */,
				AA0C67721983375C004BBF89 /* menubar-progress-3@2x.png in Resources */,
				AA0C676A1983375C004BBF89 /* menubar-error@2x.png in Resources */,
				AA0C67751983375C004BBF89 /* menubar.png in Resources */,
				AA0C676D1983375C004BBF89 /* menubar-progress-1.png in Resources */,
				AA0C676C1983375C004BBF89 /* menubar-highlighted@2x.png in Resources */,
				AA0C67731983375C004BBF89 /* menubar-success.png in Resources */,
				AA0C67741983375C004BBF89 /* menubar-success@2x.png in Resources */,
				AA9325BA19831B3E0027847E /* MainMenu.xib in Resources */,
				AA0C676B1983375C004BBF89 /* menubar-highlighted.png in Resources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		AA9325BD19831B3E0027847E /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXResourcesBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
		AA9325A919831B3E0027847E /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				AAD4434D19835ED800AA5459 /* PreferencesManager.swift in Sources */,
				AAD4434A19835E9900AA5459 /* PreferencesWindowController.swift in Sources */,
				AAD4434F1983952100AA5459 /* KeychainManager.swift in Sources */,
				AAD443511984085B00AA5459 /* SwiftyJSON.swift in Sources */,
				AA9325F119831C860027847E /* StatusItemView.swift in Sources */,
				AADCA9EE1984DFEB00304703 /* ScreenshotWatcher.m in Sources */,
				AA9325B519831B3E0027847E /* AppDelegate.swift in Sources */,
				AA0C678619835367004BBF89 /* APIClient.swift in Sources */,
				AA9325B319831B3E0027847E /* main.swift in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		AA9325BB19831B3E0027847E /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				AA9325C619831B3E0027847E /* NimbusTests.swift in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXSourcesBuildPhase section */

/* Begin PBXTargetDependency section */
		AA9325C119831B3E0027847E /* PBXTargetDependency */ = {
			isa = PBXTargetDependency;
			target = AA9325AC19831B3E0027847E /* Nimbus */;
			targetProxy = AA9325C019831B3E0027847E /* PBXContainerItemProxy */;
		};
/* End PBXTargetDependency section */

/* Begin PBXVariantGroup section */
		AA9325B819831B3E0027847E /* MainMenu.xib */ = {
			isa = PBXVariantGroup;
			children = (
				AA9325B919831B3E0027847E /* Base */,
			);
			name = MainMenu.xib;
			sourceTree = "<group>";
		};
/* End PBXVariantGroup section */

/* Begin XCBuildConfiguration section */
		AA9325C719831B3E0027847E /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				CODE_SIGN_IDENTITY = "-";
				COPY_PHASE_STRIP = NO;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				ENABLE_TESTABILITY = YES;
				GCC_C_LANGUAGE_STANDARD = gnu99;
				GCC_DYNAMIC_NO_PIC = NO;
				GCC_OPTIMIZATION_LEVEL = 0;
				GCC_PREPROCESSOR_DEFINITIONS = (
					"DEBUG=1",
					"$(inherited)",
				);
				GCC_SYMBOLS_PRIVATE_EXTERN = NO;
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				MACOSX_DEPLOYMENT_TARGET = 10.9;
				MTL_ENABLE_DEBUG_INFO = YES;
				ONLY_ACTIVE_ARCH = YES;
				SDKROOT = macosx;
				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
			};
			name = Debug;
		};
		AA9325C819831B3E0027847E /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				CODE_SIGN_IDENTITY = "-";
				COPY_PHASE_STRIP = YES;
				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
				ENABLE_NS_ASSERTIONS = NO;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				GCC_C_LANGUAGE_STANDARD = gnu99;
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				MACOSX_DEPLOYMENT_TARGET = 10.9;
				MTL_ENABLE_DEBUG_INFO = NO;
				SDKROOT = macosx;
			};
			name = Release;
		};
		AA9325CA19831B3E0027847E /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				CLANG_ENABLE_MODULES = YES;
				COMBINE_HIDPI_IMAGES = YES;
				GCC_OPTIMIZATION_LEVEL = 0;
				INFOPLIST_FILE = Nimbus/Info.plist;
				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
				PRODUCT_BUNDLE_IDENTIFIER = "com.ethanal.${PRODUCT_NAME:rfc1034identifier}";
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_OBJC_BRIDGING_HEADER = "Nimbus/Nimbus-Bridging-Header.h";
				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
			};
			name = Debug;
		};
		AA9325CB19831B3E0027847E /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				CLANG_ENABLE_MODULES = YES;
				COMBINE_HIDPI_IMAGES = YES;
				GCC_OPTIMIZATION_LEVEL = fast;
				INFOPLIST_FILE = Nimbus/Info.plist;
				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
				PRODUCT_BUNDLE_IDENTIFIER = "com.ethanal.${PRODUCT_NAME:rfc1034identifier}";
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_OBJC_BRIDGING_HEADER = "Nimbus/Nimbus-Bridging-Header.h";
				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
			};
			name = Release;
		};
		AA9325CD19831B3E0027847E /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Nimbus.app/Contents/MacOS/Nimbus";
				COMBINE_HIDPI_IMAGES = YES;
				FRAMEWORK_SEARCH_PATHS = (
					"$(DEVELOPER_FRAMEWORKS_DIR)",
					"$(inherited)",
				);
				GCC_PREPROCESSOR_DEFINITIONS = (
					"DEBUG=1",
					"$(inherited)",
				);
				INFOPLIST_FILE = NimbusTests/Info.plist;
				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
				PRODUCT_BUNDLE_IDENTIFIER = "com.ethanal.${PRODUCT_NAME:rfc1034identifier}";
				PRODUCT_NAME = "$(TARGET_NAME)";
				TEST_HOST = "$(BUNDLE_LOADER)";
			};
			name = Debug;
		};
		AA9325CE19831B3E0027847E /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Nimbus.app/Contents/MacOS/Nimbus";
				COMBINE_HIDPI_IMAGES = YES;
				FRAMEWORK_SEARCH_PATHS = (
					"$(DEVELOPER_FRAMEWORKS_DIR)",
					"$(inherited)",
				);
				INFOPLIST_FILE = NimbusTests/Info.plist;
				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
				PRODUCT_BUNDLE_IDENTIFIER = "com.ethanal.${PRODUCT_NAME:rfc1034identifier}";
				PRODUCT_NAME = "$(TARGET_NAME)";
				TEST_HOST = "$(BUNDLE_LOADER)";
			};
			name = Release;
		};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
		AA9325A819831B3E0027847E /* Build configuration list for PBXProject "Nimbus" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				AA9325C719831B3E0027847E /* Debug */,
				AA9325C819831B3E0027847E /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		AA9325C919831B3E0027847E /* Build configuration list for PBXNativeTarget "Nimbus" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				AA9325CA19831B3E0027847E /* Debug */,
				AA9325CB19831B3E0027847E /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		AA9325CC19831B3E0027847E /* Build configuration list for PBXNativeTarget "NimbusTests" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				AA9325CD19831B3E0027847E /* Debug */,
				AA9325CE19831B3E0027847E /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
/* End XCConfigurationList section */
	};
	rootObject = AA9325A519831B3E0027847E /* Project object */;
}


================================================
FILE: NimbusMenuBarApp/Nimbus/Nimbus.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
   version = "1.0">
   <FileRef
      location = "self:Nimbus.xcodeproj">
   </FileRef>
</Workspace>


================================================
FILE: NimbusMenuBarApp/Nimbus/Nimbus.xcodeproj/xcshareddata/xcschemes/Nimbus.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
   LastUpgradeVersion = "0720"
   version = "1.3">
   <BuildAction
      parallelizeBuildables = "YES"
      buildImplicitDependencies = "YES">
      <BuildActionEntries>
         <BuildActionEntry
            buildForTesting = "YES"
            buildForRunning = "YES"
            buildForProfiling = "YES"
            buildForArchiving = "YES"
            buildForAnalyzing = "YES">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "AA9325AC19831B3E0027847E"
               BuildableName = "Nimbus.app"
               BlueprintName = "Nimbus"
               ReferencedContainer = "container:Nimbus.xcodeproj">
            </BuildableReference>
         </BuildActionEntry>
      </BuildActionEntries>
   </BuildAction>
   <TestAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      shouldUseLaunchSchemeArgsEnv = "YES">
      <Testables>
         <TestableReference
            skipped = "NO">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "AA9325BE19831B3E0027847E"
               BuildableName = "NimbusTests.xctest"
               BlueprintName = "NimbusTests"
               ReferencedContainer = "container:Nimbus.xcodeproj">
            </BuildableReference>
         </TestableReference>
      </Testables>
      <MacroExpansion>
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "AA9325AC19831B3E0027847E"
            BuildableName = "Nimbus.app"
            BlueprintName = "Nimbus"
            ReferencedContainer = "container:Nimbus.xcodeproj">
         </BuildableReference>
      </MacroExpansion>
      <AdditionalOptions>
      </AdditionalOptions>
   </TestAction>
   <LaunchAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      launchStyle = "0"
      useCustomWorkingDirectory = "NO"
      ignoresPersistentStateOnLaunch = "NO"
      debugDocumentVersioning = "YES"
      debugServiceExtension = "internal"
      allowLocationSimulation = "YES">
      <BuildableProductRunnable
         runnableDebuggingMode = "0">
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "AA9325AC19831B3E0027847E"
            BuildableName = "Nimbus.app"
            BlueprintName = "Nimbus"
            ReferencedContainer = "container:Nimbus.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
      <AdditionalOptions>
         <AdditionalOption
            key = "NSZombieEnabled"
            value = "YES"
            isEnabled = "YES">
         </AdditionalOption>
      </AdditionalOptions>
   </LaunchAction>
   <ProfileAction
      buildConfiguration = "Release"
      shouldUseLaunchSchemeArgsEnv = "YES"
      savedToolIdentifier = ""
      useCustomWorkingDirectory = "NO"
      debugDocumentVersioning = "YES">
      <BuildableProductRunnable
         runnableDebuggingMode = "0">
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "AA9325AC19831B3E0027847E"
            BuildableName = "Nimbus.app"
            BlueprintName = "Nimbus"
            ReferencedContainer = "container:Nimbus.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
   </ProfileAction>
   <AnalyzeAction
      buildConfiguration = "Debug">
   </AnalyzeAction>
   <ArchiveAction
      buildConfiguration = "Release"
      revealArchiveInOrganizer = "YES">
   </ArchiveAction>
</Scheme>


================================================
FILE: NimbusMenuBarApp/Nimbus/NimbusTests/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>en</string>
	<key>CFBundleExecutable</key>
	<string>${EXECUTABLE_NAME}</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>${PRODUCT_NAME}</string>
	<key>CFBundlePackageType</key>
	<string>BNDL</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0</string>
	<key>CFBundleSignature</key>
	<string>????</string>
	<key>CFBundleVersion</key>
	<string>1</string>
</dict>
</plist>


================================================
FILE: NimbusMenuBarApp/Nimbus/NimbusTests/NimbusTests.swift
================================================
//
//  NimbusTests.swift
//  NimbusTests
//
//  Created by Ethan Lowman on 7/25/14.
//  Copyright (c) 2014 Ethanal. All rights reserved.
//

import Cocoa
import XCTest

class NimbusTests: XCTestCase {
    
    override func setUp() {
        super.setUp()
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }
    
    override func tearDown() {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
        super.tearDown()
    }
    
    func testExample() {
        // This is an example of a functional test case.
        XCTAssert(true, "Pass")
    }
    
    func testPerformanceExample() {
        // This is an example of a performance test case.
        self.measureBlock() {
            // Put the code you want to measure the time of here.
        }
    }
    
}


================================================
FILE: README.md
================================================
<h1 align="center">
  <img src="https://raw.githubusercontent.com/ethanal/Nimbus/master/graphics_assets/exports/login_logo.png" alt="Nimbus Logo">
  <br>
  Nimbus
</h1>

Nimbus is a private file sharer and URL shortener. Heavily inspired by [Cloudapp](http://www.getcloudapp.com/), Nimbus is a free and open source solution to file sharing and URL shortening that you can host yourself and fully control.

Nimbus consists of several components:

* A website that displays sharing pages for generated links and redirects shortened URLs to their targets
* A website to manage shared items
* An API to manipulate shared items
* A Mac OS X menubar app to upload files and shorten links

The menubar app is only compatible with OS X 10.9 and up since it is written in [Swift](https://developer.apple.com/swift/). The files are stored in Amazon S3, so you must have an AWS account.

## Screenshots
<table>
  <tr>
    <td><img src="https://raw.githubusercontent.com/ethanal/Nimbus/master/graphics_assets/docs/screenshot_management.png" alt="Screenshot"></td>
    <td><img src="https://raw.githubusercontent.com/ethanal/Nimbus/master/graphics_assets/docs/screenshot_img_preview.png" alt="Screenshot"></td>
  </tr>
  <tr>
    <td><img src="https://raw.githubusercontent.com/ethanal/Nimbus/master/graphics_assets/docs/screenshot_code_preview.png" alt="Screenshot"></td>
    <td><img src="https://raw.githubusercontent.com/ethanal/Nimbus/master/graphics_assets/docs/screenshot_upload.png" alt="Screenshot"></td>
  </tr>
</table>

## Features

- Share pages that show file previews or redirect to the shortened link
  - Image file previews
  - Text file previews with automatic syntax highlighting if applicable
- Screenshots are automatically uploaded and the share link is copied to the clipboard
- Drag a file or text to the menubar icon to upload it and copy the share link to the clipboard
- Drag a URL to the menubar icon to create a shortened link and copy it to the clipboard
- Keep track of view counts for files and shortened URLS

## Setup

To set up the Django app, perform the following steps on your server (assumes [pip](http://pip.readthedocs.org/en/latest/), [virtualenv](http://virtualenv.readthedocs.org/en/latest/), and [MySQL](http://www.mysql.com/) are already installed)

1. Create a virtualenv and activate it
2. Clone the repository (from here on, it is assumed that the respository's location is `/usr/local/www/Nimbus`)
3. While in the repository root, install the Python requirements by running

   ```bash
   pip install -r requirements/production.txt
   ```

4. Create a database and grant a user full access to it.
5. Follow the instructions in `nimbus/settings/secret.sample.py` to create a secrets file with your MySQL and Amazon S3 credentials
6. Set up the environment for the Django app by running

   ```bash
   export PRODUCTION=TRUE
   ```

7. Set up the database and create your user by running `./manage.py syncdb`
8. Start a Django shell (`./manage.py shell`) and run the following commands, replacing `example.com` with your domain name

   ```python
   from django.contrib.sites.models import Site
   Site.objects.update(name="example.com", domain="example.com")
   ```

9. Collect static files by running

   ```bash
   yes yes | ./manage.py collectstatic
   ```

### Serving Nimbus

Make sure you have a domain name configured with the following records:

```
@       IN A  <IP address of your server>
api     CNAME @
account CNAME @
files   CNAME files.<your domain name>.s3.amazonaws.com.
```

Also make sure you have an Amazon S3 bucket called `files.<your domain name>`

The recommended setup for serving Nimbus is [Gunicorn](http://gunicorn.org/) managed by [Supervisor](http://supervisord.org/) with [nginx](http://nginx.org/) as a reverse proxy. Configuration requirements are as follows.

* Nginx must be listening on the subdomains `account` and `api` of your domain as well as the root domain. Forward all of this traffic to Gunicorn - the Django app handles the subdomain routing.
* The attribute `client_max_body_size` must be set in the nginx config to a sufficiently large value to allow uploads of big files.
* Static file requests (`/static/`) should be aliased to `nimbus/collected_static` in the repository root
* Supervisor must call the version of gunicorn in your virtualenv

#### Example Supervisor Configuration

```ini
[program:nimbus]
directory = /usr/local/www/Nimbus
user = nobody
command = /usr/local/virtualenvs/Nimbus/bin/gunicorn nimbus.wsgi:application --user=nobody --workers=1 --bind=127.0.0.1:8080
environment = PRODUCTION=TRUE
stdout_logfile = /var/log/sites/nimbus.gunicorn.log
stderr_logfile = /var/log/sites/nimbus.gunicorn.log
autostart = true
autorestart = true
```

#### Example Nginx Configuration
```nginx
server {
    listen 80;
    server_name example.com account.example.com api.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name example.com account.example.com api.example.com;

    ssl on;
    ssl_certificate /usr/local/certs/example.com.crt;
    ssl_certificate_key /usr/local/certs/example.com.key;

    client_max_body_size 1024M;

    access_log /var/log/sites/nimbus.access.log;
    error_log /var/log/sites/nimbus.error.log;

    location /favicon.ico {
        alias /usr/local/www/Nimbus/nimbus/static/img/favicon.ico;
    }

    location /static/ {
        alias /usr/local/www/Nimbus/nimbus/collected_static/;
    }

    location / {
        rewrite ^/((?!(api-auth|admin))(.*))/$ /$1 permanent;
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```

## API Reference
API documentation can be found [here](api_docs.md).

## Contact
Ethan Lowman
- https://github.com/ethanal
- ethan@ethanlowman.com


================================================
FILE: api_docs.md
================================================
# API Reference

The Nimbus API uses token authentication, so it is recommended that you secure the `account` and `api` subdomains with HTTPS. The `Authorization` header for requests that require it should have the following form:

```
Token 81d1445d307741e63f5c6f26d4e840175c21a34d
```

The term "media item" refers to a file or a shortened link.

***

## Obtain Authorization Token
Obtain the API authorization token corresponding to a username/password pair.

### Request
- Requires: Authentication
- HTTP Request Method: `POST`
- URL: `/api-token-auth`
- Parameters
  - `username`: The username for the user whose token should be returned
  - `password`: The password for the user whose token should be returned

### Response
- Status: 200 OK
- Body:

  ```js
  {
      "token": "81d1445d307741e63f5c6f26d4e840175c21a34d"
  }
  ```

### Errors

If the provided credentials are not valid, the following response is returned:

- Status: 400 Bad Request
- Body:

  ```js
  {
      "non_field_errors": [
          "Unable to login with provided credentials."
      ]
  }
  ```

***

## List Media Items
List all media items created by the authorized user.

All media items are serialized the same way. Links and files can be differentiated using the `media_type` attribute.

Valid media type codes are as follows:

- `URL`: Shortened URLS
- `IMG`: Image files
- `TXT`: Text files
- `ARC`: Archive files
- `AUD`: Audio files
- `VID`: Video files
- `ETC`: Other files

### Request
- Requires: Authentication
- HTTP Request Method: `GET`
- URL: `/media/list`
- Optional URL Parameters:
  - `media_type`: The media type code used to filter the list (e.g. `GET /media/list?media_type=IMG` will list all images)

### Response
- Status: 200 OK
- Body:

  ```js
  [
      {
          "url_hash": "clJwWj",
          "share_url": "http://example.com/clJwWj",
          "name": "example.png",
          "target_url": "",
          "target_file": "de807a6626ed47c1adf3696bfb2cb9ef/example.png",
          "target_file_url": "http://files.example.com/de807a6626ed47c1adf3696bfb2cb9ef/example.png",
          "view_count": 4,
          "upload_date": "2014-01-02T03:04:05.060Z",
          "media_type": "IMG"
      },
      {
          "url_hash": "i7UrcU",
          "share_url": "http://example.com/i7UrcU",
          "name": "http://en.wikipedia.org/wiki/Example",
          "target_url": "http://en.wikipedia.org/wiki/Example",
          "target_file": "",
          "target_file_url": "",
          "view_count": 2,
          "upload_date": "upload_date": "2014-01-02T03:45:06.070Z",
          "media_type": "URL"
      }
  ]
  ```

***

## Show Media Item Details
Show details for a media item.

### Request
- Requires: Authentication
- HTTP Request Method: `GET`
- URL: `/media/show`
- URL Parameters:
  - `url_hash`: The media type code used to filter the list (e.g. `GET /media/list?media_type=IMG` will list all images)

### Response
- Status: 200 OK
- Body:

  ```js
  {
      "url_hash": "clJwWj",
      "share_url": "http://example.com/clJwWj",
      "name": "example.png",
      "target_url": "",
      "target_file": "de807a6626ed47c1adf3696bfb2cb9ef/example.png",
      "target_file_url": "http://files.example.com/de807a6626ed47c1adf3696bfb2cb9ef/example.png",
      "view_count": 4,
      "upload_date": "2014-01-02T03:04:05.060Z",
      "media_type": "IMG"
  }
  ```

***

## Upload File
Create a media item for a file.

### Request
- Requires: Authentication
- HTTP Request Method: `POST`
- URL: `/media/add_file`
- Parameters
  - `file`: The file to upload.

### Response
- Status: 201 Created
- Body:

  ```js
  {
      "url_hash": "clJwWj",
      "share_url": "http://example.com/clJwWj",
      "name": "example.png",
      "target_file": "de807a6626ed47c1adf3696bfb2cb9ef/example.png",
      "target_file_url": "http://files.example.com/de807a6626ed47c1adf3696bfb2cb9ef/example.png",
      "upload_date": "2014-01-02T03:04:05.060Z",
      "media_type": "IMG"
  }
  ```

***

## Add Link
Create a media item for a URL.

### Request
- Requires: Authentication
- HTTP Request Method: `POST`
- URL: `/media/add_link`
- Parameters
  - `target_url`: The URL to shorten.

### Response
- Status: 201 Created
- Body:

  ```js
  {
      "url_hash": "i7UrcU",
      "share_url": "http://example.com/i7UrcU",
      "target_url": "http://en.wikipedia.org/wiki/Example",
      "upload_date": "2014-01-02T03:45:06.070Z"
  }
  ```

***

## Delete Media Items
Delete one or more media items.

### Request
- Requires: Authentication
- HTTP Request Method: `DELETE`
- URL: `/media/delete`
- URL Parameters
  - `url_hash`: The URL hash of the media item that should be deleted. This parameter can be repeated to delete multiple items.

### Response
- Status: 204 No Content

***


================================================
FILE: graphics_assets/assets.sketch/metadata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>app</key>
	<string>com.bohemiancoding.sketch3</string>
	<key>build</key>
	<integer>8054</integer>
	<key>commit</key>
	<string>b2079fe10151ad0eef6cc553b7369ec78d67b9b5</string>
	<key>fonts</key>
	<array/>
	<key>length</key>
	<integer>193569</integer>
	<key>version</key>
	<integer>37</integer>
</dict>
</plist>


================================================
FILE: graphics_assets/assets.sketch/version
================================================
37

================================================
FILE: manage.py
================================================
#!/usr/bin/env python
import os
import sys


if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "nimbus.settings")

    from django.core.management import execute_from_command_line

    execute_from_command_line(sys.argv)


================================================
FILE: nimbus/__init__.py
================================================


================================================
FILE: nimbus/apps/__init__.py
================================================
from django.conf.urls import patterns, include, url
from nimbus import settings


def debug_urls():
    if settings.SHOW_DEBUG_TOOLBAR:
        import debug_toolbar

        return patterns("",
            url(r"^__debug__/", include(debug_toolbar.urls)),
        )
    return []


================================================
FILE: nimbus/apps/accounts/__init__.py
================================================


================================================
FILE: nimbus/apps/accounts/forms.py
================================================
import logging
from django.contrib.auth.forms import AuthenticationForm
from django import forms
from django.utils.html import strip_tags


logger = logging.getLogger(__name__)


class AuthenticateForm(AuthenticationForm):
    username = forms.CharField(required=True, widget=forms.widgets.TextInput(attrs={"placeholder": "Username"}), error_messages={"required": "Invalid username"})
    password = forms.CharField(required=True, widget=forms.widgets.PasswordInput(attrs={"placeholder": "Password"}), error_messages={"required": "Invalid password"})

    def is_valid(self):
        form = super(AuthenticateForm, self).is_valid()

        for f, error in self.errors.iteritems():
            if f == "__all__":
                self.fields["password"].widget.attrs.update({"class": "error", "placeholder": "Invalid password"})
            else:
                self.fields[f].widget.attrs.update({"class": "error", "placeholder": strip_tags(str(error))})
        logger.debug(self.errors)
        return form


class UploadFileForm(forms.Form):
    file = forms.FileField()


================================================
FILE: nimbus/apps/accounts/urls.py
================================================
from django.conf.urls import include, url
from django.contrib import admin
from . import views
from nimbus.apps import debug_urls


urlpatterns = debug_urls()

admin.autodiscover()

urlpatterns += [
    url(r'^admin/', include(admin.site.urls)),
    url(r"^$", views.index, name="index"),
    url(r"^login$", views.login_view.as_view(), name="login"),
    url(r"^logout$", views.logout_view, name="logout"),
    url(r"^(?P<media_type>(images)|(links)|(text)|(archives)|(audio)|(video)|(other))$", views.dashboard_view, name="filter_media"),
]


================================================
FILE: nimbus/apps/accounts/views.py
================================================
import logging
from .forms import AuthenticateForm
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.shortcuts import render, redirect
from django.contrib.auth import login, logout
from django.views.generic.base import View
from nimbus.apps.media.models import Media


logger = logging.getLogger(__name__)


def index(request, auth_form=None):
    if request.user.is_authenticated():
        return dashboard_view(request)
    else:
        auth_form = auth_form or AuthenticateForm()
        request.session.set_test_cookie()
        return render(request, "nimbus/accounts/login.html", {
            "auth_form": auth_form
        })


class login_view(View):
    def post(self, request):
        form = AuthenticateForm(data=request.POST)

        if request.session.test_cookie_worked():
            request.session.delete_test_cookie()
        else:
            logger.error("No cookie support detected! This could cause problems.")

        if form.is_valid():
            login(request, form.get_user())
            logger.info("Login succeeded as {}".format(request.POST.get("username", "unknown")))
            next = request.GET.get("next", "/")
            return redirect(next)
        else:
            logger.info("Login failed as {}".format(request.POST.get("username", "unknown")))
            return index(request, auth_form=form)  # Modified to show errors

    def get(self, request):
        return index(request)


def logout_view(request):
    logout(request)
    return redirect("/")


@login_required
def dashboard_view(request, media_type="all"):
    media_type_codes = {j.lower(): i for i, j in Media.MEDIA_TYPES_PLURAL}
    if media_type == "all":
        media_list = Media.objects.filter(user=request.user).order_by("-upload_date", "name")
    else:
        media_list = Media.objects.filter(user=request.user, media_type=media_type_codes[media_type]).order_by("-upload_date", "name")

    paginator = Paginator(media_list, 50)
    page = request.GET.get("p")
    try:
        media = paginator.page(page)
    except PageNotAnInteger:
        media = paginator.page(1)
    except EmptyPage:
        media = paginator.page(paginator.num_pages)

    return render(request, "nimbus/accounts/dashboard.html", {
        "media_type": media_type,
        "media_type_code": media_type_codes.get(media_type, "ALL"),
        "media": media
    })


================================================
FILE: nimbus/apps/api/__init__.py
================================================


================================================
FILE: nimbus/apps/api/urls.py
================================================
from django.conf.urls import url, include
from nimbus.apps import debug_urls
from . import views


urlpatterns = debug_urls()

urlpatterns += [
    url(r"^$", views.api_root, name="api_root"),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
    url(r'^api-token-auth$', 'rest_framework.authtoken.views.obtain_auth_token'),
    url(r"^media/list$", views.MediaList.as_view(), name="media_list"),
    url(r"^media/show$", views.MediaDetail.as_view(), name="media_detail"),
    url(r"^media/add_file$", views.AddFile.as_view(), name="add_file"),
    url(r"^media/add_link$", views.AddLink.as_view(), name="add_link"),
    url(r"^media/delete$", views.delete_media, name="delete_media"),
]


================================================
FILE: nimbus/apps/api/views.py
================================================
from django.shortcuts import get_object_or_404
from django.template.loader import render_to_string
from rest_framework import generics, views, status
from rest_framework.decorators import api_view
from rest_framework.exceptions import APIException
from rest_framework.response import Response
from rest_framework.parsers import MultiPartParser
from nimbus.apps.media.models import Media
from nimbus.apps.media.serializers import MediaSerializer, CreateLinkSerializer, ViewCreatedFileSerializer, ViewCreatedLinkSerializer


class URLHashParameterRequiredException(APIException):
    status_code = 400
    default_detail = "'url_hash' parameter required"


@api_view(("GET",))
def api_root(request):
    """Welcome to the Nimbus API!
    Documentation can be found at [github.com/ethanal/nimbus](http://github.com/ethanal/nimbus)
    """

    return Response("Documentation can be found at http://github.com/ethanal/nimbus")


class MediaList(generics.ListAPIView):
    serializer_class = MediaSerializer

    def get_queryset(self):
        user = self.request.user
        if "media_type" in self.request.QUERY_PARAMS:
            return Media.objects.filter(user=user, media_type=self.requeset.QUERY_PARAMS["media_type"])
        return Media.objects.filter(user=user)


class MediaDetail(generics.RetrieveAPIView):
    serializer_class = MediaSerializer

    def get_object(self):
        if "url_hash" not in self.request.QUERY_PARAMS:
            raise URLHashParameterRequiredException()

        queryset = Media.objects.filter(user=self.request.user)
        return get_object_or_404(queryset, url_hash=self.request.QUERY_PARAMS["url_hash"])


class AddFile(views.APIView):
    parser_classes = (MultiPartParser,)

    def post(self, request):
        f = request.FILES.get("file", None)

        if not f:
            return Response(status=400)

        media_item = Media(name=f.name, user=request.user, target_file=f)
        media_item.save()
        if media_item.media_type == "TXT":
            text = f.file.getvalue()
            media_item.fill_syntax_highlighted(text)

        data = ViewCreatedFileSerializer(media_item).data

        if "include-html" in request.QUERY_PARAMS:
            context = {
                "media_item": media_item
            }
            html = render_to_string("nimbus/accounts/media_table_row.html", context)
            data["html"] = html

        return Response(data, status=201)


class AddLink(generics.CreateAPIView):
    serializer_class = CreateLinkSerializer

    def pre_save(self, obj):
        obj.user = self.request.user
        obj.name = obj.target_url

    def create(self, request, *args, **kwargs):
        response = super(AddLink, self).create(request, *args, **kwargs)
        if response.status_code == status.HTTP_201_CREATED:
            response.data = ViewCreatedLinkSerializer(self.object).data
        return response


@api_view(("DELETE",))
def delete_media(request):
    user = request.user
    hashes = request.QUERY_PARAMS.getlist("url_hash")
    Media.objects.filter(user=user, url_hash__in=hashes).delete()
    return Response(status=204)


================================================
FILE: nimbus/apps/media/__init__.py
================================================


================================================
FILE: nimbus/apps/media/admin.py
================================================
from django.contrib import admin
from .models import Media



class MediaAdmin(admin.ModelAdmin):
    fields = ("url_hash",
              "name",
              "target_url",
              "target_file",
              "user",
              "syntax_highlighted")
    list_display = ("id",
                    "url_hash",
                    "name",
                    "user",
                    "target_url",
                    "target_file",
                    "upload_date",
                    "view_count",
                    "media_type")
    list_filter = ("media_type",)
    search_fields = ("id",
                     "url_hash",
                     "name",
                     "user__username",
                     "target_url",
                     "target_file",
                     "upload_date",
                     "view_count",
                     "media_type")

admin.site.register(Media, MediaAdmin)


================================================
FILE: nimbus/apps/media/models.py
================================================
import mimetypes
import uuid
import string
import random
from pygments import highlight
from pygments.lexers import guess_lexer_for_filename
from pygments.formatters import HtmlFormatter
from django.db import models
from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from django.contrib.auth import get_user_model
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.core.validators import URLValidator
from django.utils.encoding import filepath_to_uri
from rest_framework.authtoken.models import Token


class Media(models.Model):
    MEDIA_TYPES = (
        ("IMG", "Image"),
        ("URL", "Link"),
        ("TXT", "Text"),
        ("ARC", "Archive"),
        ("AUD", "Audio"),
        ("VID", "Video"),
        ("ETC", "Other")
    )

    MEDIA_TYPES_PLURAL = (
        ("IMG", "Images"),
        ("URL", "Links"),
        ("TXT", "Text"),
        ("ARC", "Archives"),
        ("AUD", "Audio"),
        ("VID", "Video"),
        ("ETC", "Other")
    )

    _random_filename = lambda i, f: str(uuid.uuid4()).replace("-", "") + "/" + f

    url_hash = models.CharField(max_length=100, blank=True)
    name = models.CharField(max_length=500)
    target_url = models.URLField(max_length=2048, blank=True)
    target_file = models.FileField(upload_to=_random_filename, blank=True)
    view_count = models.IntegerField(default=0)
    upload_date = models.DateTimeField(auto_now_add=True)
    user = models.ForeignKey(User)
    media_type = models.CharField(max_length=3, choices=MEDIA_TYPES, blank=True)
    syntax_highlighted = models.TextField(blank=True)

    # all prefixed by "application/"
    ARCHIVE_MIME_TYPES = ["x-cpio",
                          "x-shar",
                          "x-tar",
                          "x-bzip2",
                          "x-gzip",
                          "x-lzip",
                          "x-lzma",
                          "x-lzop",
                          "x-xz",
                          "x-compress",
                          "x-compress",
                          "x-7z-compressed",
                          "x-ace-compressed",
                          "x-astrotite-afa",
                          "x-alz-compressed",
                          "vnd.android.package-archive",
                          "x-arj",
                          "x-b1",
                          "vnd.ms-cab-compressed",
                          "x-cfs-compressed",
                          "x-dar",
                          "x-dgc-compressed",
                          "x-apple-diskimage",
                          "x-gca-compressed",
                          "x-lzh",
                          "x-lzx",
                          "x-rar-compressed",
                          "x-stuffit",
                          "x-stuffitx",
                          "x-gtar",
                          "zip",
                          "x-zoo",
                          "x-par2"]

    @property
    def raw_url(self):
        return self.target_file.storage.url(self.target_file.name)

    @property
    def raw_ssl_url(self):
        storage = self.target_file.storage
        name = storage._normalize_name(storage._clean_name(self.target_file.name))
        return "https://s3.amazonaws.com/{}/{}".format(storage.bucket_name, filepath_to_uri(name))

    @staticmethod
    def guess_media_type(resource_name):
        validator = URLValidator()
        try:
            validator(resource_name)
        except ValidationError:
            pass
        else:
            return "URL"

        top, sub = (mimetypes.guess_type(resource_name, strict=False)[0] or "/").split("/")
        if top == "image":
            return "IMG"
        if top == "text":
            return "TXT"
        if top == "audio":
            return "AUD"
        if top == "video":
            return "VID"
        if (top == "application") and (sub in Media.ARCHIVE_MIME_TYPES):
            return "ARC"
        return "ETC"

    def fill_syntax_highlighted(self, text):
        lexer = guess_lexer_for_filename(self.name, text)
        html = highlight(text, lexer, HtmlFormatter())
        self.syntax_highlighted = html
        self.save()

    def __unicode__(self):
        return self.name

    class Meta:
        verbose_name_plural = "Media"


@receiver(post_save, sender=Media)
def fill_auto_fields(sender, **kwargs):
    instance = kwargs.get("instance")

    fields = {}
    if not instance.media_type:
        fields["media_type"] = Media.guess_media_type(instance.name)
    if not instance.url_hash:
        url_hash = None
        alphabet = string.uppercase + string.lowercase + string.digits
        length = 5
        tries = 0
        while url_hash is None or Media.objects.filter(url_hash=url_hash).exists():
            url_hash = "".join(random.SystemRandom().choice(alphabet) for _ in range(length))
            tries += 1
            if tries % 3 == 0:
                length += 1
        fields["url_hash"] = url_hash

    if fields:
        for field, value in fields.items():
            setattr(instance, field, value)
        instance.save()


@receiver(pre_delete, sender=Media)
def delete_file_from_storage(sender, **kwargs):
    instance = kwargs.get("instance")

    if instance.target_file:
        instance.target_file.delete()


# No real better place to put this...
@receiver(post_save, sender=get_user_model())
def create_auth_token(sender, instance=None, created=False, **kwargs):
    if created:
        Token.objects.create(user=instance)


================================================
FILE: nimbus/apps/media/serializers.py
================================================
from rest_framework import serializers
from subdomains.utils import reverse
from nimbus.apps.media.models import Media


def _get_target_file_url(obj):
    if obj.target_file:
        return obj.raw_url
    return ""


def _get_share_url(obj):
    return reverse("share",
                   subdomain=None,
                   kwargs={"url_hash": obj.url_hash})


class MediaSerializer(serializers.ModelSerializer):
    target_file_url = serializers.SerializerMethodField("get_target_file_url")
    share_url = serializers.SerializerMethodField("get_share_url")

    def get_target_file_url(self, obj):
        return _get_target_file_url(obj)

    def get_share_url(self, obj):
        return _get_share_url(obj)

    class Meta:
        model = Media
        fields = ("url_hash",
                  "share_url",
                  "name",
                  "target_url",
                  "target_file",
                  "target_file_url",
                  "view_count",
                  "upload_date",
                  "media_type")


class ViewCreatedFileSerializer(serializers.ModelSerializer):
    target_file_url = serializers.SerializerMethodField("get_target_file_url")
    share_url = serializers.SerializerMethodField("get_share_url")

    def get_target_file_url(self, obj):
        return _get_target_file_url(obj)

    def get_share_url(self, obj):
        return _get_share_url(obj)

    class Meta:
        model = Media
        fields = ("url_hash",
                  "share_url",
                  "name",
                  "target_file",
                  "target_file_url",
                  "upload_date",
                  "media_type")


class CreateLinkSerializer(serializers.ModelSerializer):
    target_url = serializers.URLField(max_length=2048)

    class Meta:
        model = Media
        fields = ("target_url",)


class ViewCreatedLinkSerializer(serializers.ModelSerializer):
    share_url = serializers.SerializerMethodField("get_share_url")

    def get_share_url(self, obj):
        return _get_share_url(obj)

    class Meta:
        model = Media
        fields = ("url_hash",
                  "share_url",
                  "target_url",
                  "upload_date")


================================================
FILE: nimbus/apps/media/urls.py
================================================
from django.conf.urls import url
from django.views.generic.base import RedirectView
from subdomains.utils import reverse
from nimbus.apps import debug_urls
from nimbus.apps.media import views

urlpatterns = debug_urls()

urlpatterns += [
    url(r"^$", RedirectView.as_view(url=reverse("index", subdomain="account"))),
    url(r"^(?P<url_hash>[a-zA-Z0-9]+)$", views.share_view, name="share"),
]


================================================
FILE: nimbus/apps/media/views.py
================================================
from pygments.formatters import HtmlFormatter
from django.shortcuts import render, get_object_or_404, redirect
from .models import Media


def share_view(request, url_hash):
    media_item = get_object_or_404(Media, url_hash=url_hash)
    media_item.view_count += 1
    media_item.save()

    if media_item.media_type == "URL":
        return redirect(media_item.target_url)

    templates = {
        "IMG": "nimbus/media/share_img_preview.html",
        "TXT": "nimbus/media/share_txt_preview.html"
    }
    template = templates.setdefault(media_item.media_type, "nimbus/media/share_download.html")

    context = {
        "media_item": media_item
    }

    if media_item.media_type == "TXT":
        context["syntax_highlighting_style_defs"] = HtmlFormatter().get_style_defs('.highlight')

    return render(request, template, context)


================================================
FILE: nimbus/settings/__init__.py
================================================
import os
import sys

if os.getenv("PRODUCTION", "FALSE") == "TRUE":
    from production import *
else:
    from local import *


================================================
FILE: nimbus/settings/base.py
================================================
import os
import re
from fnmatch import fnmatch
from boto.s3.connection import VHostCallingFormat
from secret import *


PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

LOGIN_URL = "/login"
LOGIN_REDIRECT_URL = "/"

ALLOWED_HOSTS = ["." + HOSTNAME]

APPEND_SLASH = False


ADMINS = (
    # ('Your Name', 'your_email@example.com'),
)

MANAGERS = ADMINS

# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# In a Windows environment this must be set to your system time zone.
TIME_ZONE = "America/New_York"

# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = "en-us"

SITE_ID = 1

# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = False

# If you set this to False, Django will not format dates, numbers and
# calendars according to the current locale.
USE_L10N = True

# If you set this to False, Django will not use timezone-aware datetimes.
USE_TZ = True

# Absolute path to the directory static files should be collected to.
# Don't put anything in this directory yourself; store your static files
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
# Example: "/home/media/media.lawrence.com/static/"
STATIC_ROOT = os.path.join(PROJECT_ROOT, "collected_static")

# URL prefix for static files.
# Example: "http://media.lawrence.com/static/"
STATIC_URL = "/static/"

# Additional locations of static files
STATICFILES_DIRS = (
    # Put strings here, like "/home/html/static" or "C:/www/django/static".
    # Always use forward slashes, even on Windows.
    # Don't forget to use absolute paths, not relative paths.
    os.path.join(PROJECT_ROOT, "static"),
)

# List of finder classes that know how to find static files in
# various locations.
STATICFILES_FINDERS = (
    'django.contrib.staticfiles.finders.FileSystemFinder',
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
#    'django.contrib.staticfiles.finders.DefaultStorageFinder',
)

# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
    'django.template.loaders.filesystem.Loader',
    'django.template.loaders.app_directories.Loader',
#     'django.template.loaders.eggs.Loader',
)

TEMPLATE_CONTEXT_PROCES
Download .txt
gitextract_4bhtxnk0/

├── .gitignore
├── LICENSE
├── NimbusMenuBarApp/
│   └── Nimbus/
│       ├── Nimbus/
│       │   ├── APIClient.swift
│       │   ├── AppDelegate.swift
│       │   ├── Base.lproj/
│       │   │   └── MainMenu.xib
│       │   ├── Images.xcassets/
│       │   │   └── AppIcon.appiconset/
│       │   │       └── Contents.json
│       │   ├── Info.plist
│       │   ├── KeychainManager.swift
│       │   ├── Nimbus-Bridging-Header.h
│       │   ├── PreferencesManager.swift
│       │   ├── PreferencesWindowController.swift
│       │   ├── PreferencesWindowController.xib
│       │   ├── ScreenshotWatcher.h
│       │   ├── ScreenshotWatcher.m
│       │   ├── StatusItemView.swift
│       │   ├── SwiftyJSON.swift
│       │   └── main.swift
│       ├── Nimbus.xcodeproj/
│       │   ├── project.pbxproj
│       │   ├── project.xcworkspace/
│       │   │   └── contents.xcworkspacedata
│       │   └── xcshareddata/
│       │       └── xcschemes/
│       │           └── Nimbus.xcscheme
│       └── NimbusTests/
│           ├── Info.plist
│           └── NimbusTests.swift
├── README.md
├── api_docs.md
├── graphics_assets/
│   └── assets.sketch/
│       ├── Data
│       ├── metadata
│       └── version
├── manage.py
├── nimbus/
│   ├── __init__.py
│   ├── apps/
│   │   ├── __init__.py
│   │   ├── accounts/
│   │   │   ├── __init__.py
│   │   │   ├── forms.py
│   │   │   ├── urls.py
│   │   │   └── views.py
│   │   ├── api/
│   │   │   ├── __init__.py
│   │   │   ├── urls.py
│   │   │   └── views.py
│   │   └── media/
│   │       ├── __init__.py
│   │       ├── admin.py
│   │       ├── models.py
│   │       ├── serializers.py
│   │       ├── urls.py
│   │       └── views.py
│   ├── settings/
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── local.py
│   │   ├── production.py
│   │   └── secret.sample.py
│   ├── static/
│   │   ├── css/
│   │   │   ├── base.css
│   │   │   ├── dashboard.css
│   │   │   ├── login.css
│   │   │   └── share_preview.css
│   │   └── js/
│   │       ├── dashboard.js
│   │       └── jquery-photo-resize.js
│   ├── templates/
│   │   ├── admin/
│   │   │   └── base_site.html
│   │   ├── nimbus/
│   │   │   ├── accounts/
│   │   │   │   ├── dashboard.html
│   │   │   │   ├── login.html
│   │   │   │   └── media_table_row.html
│   │   │   ├── media/
│   │   │   │   ├── share_base.html
│   │   │   │   ├── share_download.html
│   │   │   │   ├── share_img_preview.html
│   │   │   │   └── share_txt_preview.html
│   │   │   └── page_base.html
│   │   └── rest_framework/
│   │       ├── api.html
│   │       └── login.html
│   ├── utils.py
│   └── wsgi.py
└── requirements/
    ├── base.txt
    ├── local.txt
    └── production.txt
Download .txt
SYMBOL INDEX (57 symbols across 11 files)

FILE: nimbus/apps/__init__.py
  function debug_urls (line 5) | def debug_urls():

FILE: nimbus/apps/accounts/forms.py
  class AuthenticateForm (line 10) | class AuthenticateForm(AuthenticationForm):
    method is_valid (line 14) | def is_valid(self):
  class UploadFileForm (line 26) | class UploadFileForm(forms.Form):

FILE: nimbus/apps/accounts/views.py
  function index (line 14) | def index(request, auth_form=None):
  class login_view (line 25) | class login_view(View):
    method post (line 26) | def post(self, request):
    method get (line 43) | def get(self, request):
  function logout_view (line 47) | def logout_view(request):
  function dashboard_view (line 53) | def dashboard_view(request, media_type="all"):

FILE: nimbus/apps/api/views.py
  class URLHashParameterRequiredException (line 12) | class URLHashParameterRequiredException(APIException):
  function api_root (line 18) | def api_root(request):
  class MediaList (line 26) | class MediaList(generics.ListAPIView):
    method get_queryset (line 29) | def get_queryset(self):
  class MediaDetail (line 36) | class MediaDetail(generics.RetrieveAPIView):
    method get_object (line 39) | def get_object(self):
  class AddFile (line 47) | class AddFile(views.APIView):
    method post (line 50) | def post(self, request):
  class AddLink (line 74) | class AddLink(generics.CreateAPIView):
    method pre_save (line 77) | def pre_save(self, obj):
    method create (line 81) | def create(self, request, *args, **kwargs):
  function delete_media (line 89) | def delete_media(request):

FILE: nimbus/apps/media/admin.py
  class MediaAdmin (line 6) | class MediaAdmin(admin.ModelAdmin):

FILE: nimbus/apps/media/models.py
  class Media (line 19) | class Media(models.Model):
    method raw_url (line 88) | def raw_url(self):
    method raw_ssl_url (line 92) | def raw_ssl_url(self):
    method guess_media_type (line 98) | def guess_media_type(resource_name):
    method fill_syntax_highlighted (line 120) | def fill_syntax_highlighted(self, text):
    method __unicode__ (line 126) | def __unicode__(self):
    class Meta (line 129) | class Meta:
  function fill_auto_fields (line 134) | def fill_auto_fields(sender, **kwargs):
  function delete_file_from_storage (line 159) | def delete_file_from_storage(sender, **kwargs):
  function create_auth_token (line 168) | def create_auth_token(sender, instance=None, created=False, **kwargs):

FILE: nimbus/apps/media/serializers.py
  function _get_target_file_url (line 6) | def _get_target_file_url(obj):
  function _get_share_url (line 12) | def _get_share_url(obj):
  class MediaSerializer (line 18) | class MediaSerializer(serializers.ModelSerializer):
    method get_target_file_url (line 22) | def get_target_file_url(self, obj):
    method get_share_url (line 25) | def get_share_url(self, obj):
    class Meta (line 28) | class Meta:
  class ViewCreatedFileSerializer (line 41) | class ViewCreatedFileSerializer(serializers.ModelSerializer):
    method get_target_file_url (line 45) | def get_target_file_url(self, obj):
    method get_share_url (line 48) | def get_share_url(self, obj):
    class Meta (line 51) | class Meta:
  class CreateLinkSerializer (line 62) | class CreateLinkSerializer(serializers.ModelSerializer):
    class Meta (line 65) | class Meta:
  class ViewCreatedLinkSerializer (line 70) | class ViewCreatedLinkSerializer(serializers.ModelSerializer):
    method get_share_url (line 73) | def get_share_url(self, obj):
    class Meta (line 76) | class Meta:

FILE: nimbus/apps/media/views.py
  function share_view (line 6) | def share_view(request, url_hash):

FILE: nimbus/settings/base.py
  class glob_list (line 171) | class glob_list(list):
    method __contains__ (line 174) | def __contains__(self, key):

FILE: nimbus/static/js/jquery-photo-resize.js
  function updatePhotoSize (line 20) | function updatePhotoSize() {

FILE: nimbus/utils.py
  class PatchDepatchRefererCsrf (line 9) | class PatchDepatchRefererCsrf(CorsMiddleware, CorsPostCsrfMiddleware):
    method patch (line 15) | def patch(self, request):
    method depatch (line 19) | def depatch(self, request):
  class SessionAuthentication (line 23) | class SessionAuthentication(RFSessionAuthentication):
    method enforce_csrf (line 31) | def enforce_csrf(self, request):
Condensed preview — 70 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (245K chars).
[
  {
    "path": ".gitignore",
    "chars": 1103,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\n"
  },
  {
    "path": "LICENSE",
    "chars": 1078,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2014 Ethan Lowman\n\nPermission is hereby granted, free of charge, to any person obta"
  },
  {
    "path": "NimbusMenuBarApp/Nimbus/Nimbus/APIClient.swift",
    "chars": 5636,
    "preview": "\n//\n//  APIClient.swift\n//  Nimbus\n//\n//  Created by Ethan Lowman on 7/25/14.\n//  Copyright (c) 2014 Ethanal. All rights"
  },
  {
    "path": "NimbusMenuBarApp/Nimbus/Nimbus/AppDelegate.swift",
    "chars": 2386,
    "preview": "//\n//  AppDelegate.swift\n//  Nimbus\n//\n//  Created by Ethan Lowman on 7/25/14.\n//  Copyright (c) 2014 Ethanal. All right"
  },
  {
    "path": "NimbusMenuBarApp/Nimbus/Nimbus/Base.lproj/MainMenu.xib",
    "chars": 47209,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3"
  },
  {
    "path": "NimbusMenuBarApp/Nimbus/Nimbus/Images.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 1300,
    "preview": "{\n  \"images\" : [\n    {\n      \"size\" : \"16x16\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"icon_16x16.png\",\n      \"scale\""
  },
  {
    "path": "NimbusMenuBarApp/Nimbus/Nimbus/Info.plist",
    "chars": 1111,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "NimbusMenuBarApp/Nimbus/Nimbus/KeychainManager.swift",
    "chars": 3218,
    "preview": "\nimport Cocoa\nimport Security\n\n// Identifiers\nlet serviceIdentifier = \"Nimbus\"\nlet userAccount = \"authenticatedUser\"\nlet"
  },
  {
    "path": "NimbusMenuBarApp/Nimbus/Nimbus/Nimbus-Bridging-Header.h",
    "chars": 133,
    "preview": "//\n//  Use this file to import your target's public headers that you would like to expose to Swift.\n//\n\n#import \"Screens"
  },
  {
    "path": "NimbusMenuBarApp/Nimbus/Nimbus/PreferencesManager.swift",
    "chars": 1391,
    "preview": "//\n//  PreferencesManager.swift\n//  Nimbus\n//\n//  Created by Ethan Lowman on 7/25/14.\n//  Copyright (c) 2014 Ethanal. Al"
  },
  {
    "path": "NimbusMenuBarApp/Nimbus/Nimbus/PreferencesWindowController.swift",
    "chars": 3029,
    "preview": "//\n//  PreferencesWindowController.swift\n//  Nimbus\n//\n//  Created by Ethan Lowman on 7/25/14.\n//  Copyright (c) 2014 Et"
  },
  {
    "path": "NimbusMenuBarApp/Nimbus/Nimbus/PreferencesWindowController.xib",
    "chars": 10957,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3"
  },
  {
    "path": "NimbusMenuBarApp/Nimbus/Nimbus/ScreenshotWatcher.h",
    "chars": 452,
    "preview": "//\n//  ScreenshotWatch.h\n//  Nimbus\n//\n//  Created by Ethan Lowman on 7/27/14.\n//  Copyright (c) 2014 Ethanal. All right"
  },
  {
    "path": "NimbusMenuBarApp/Nimbus/Nimbus/ScreenshotWatcher.m",
    "chars": 5386,
    "preview": "//\n//  ScreenshotWatch.m\n//  Nimbus\n//\n//  Created by Ethan Lowman on 7/27/14.\n//  Copyright (c) 2014 Ethanal. All right"
  },
  {
    "path": "NimbusMenuBarApp/Nimbus/Nimbus/StatusItemView.swift",
    "chars": 7658,
    "preview": "//\n//  StatusItemView.swift\n//  Nimbus\n//\n//  Created by Ethan Lowman on 7/25/14.\n//  Copyright (c) 2014 Ethanal. All ri"
  },
  {
    "path": "NimbusMenuBarApp/Nimbus/Nimbus/SwiftyJSON.swift",
    "chars": 37181,
    "preview": "//  SwiftyJSON.swift\n//\n//  Copyright (c) 2014 Ruoyu Fu, Pinglin Tang\n//\n//  Permission is hereby granted, free of charg"
  },
  {
    "path": "NimbusMenuBarApp/Nimbus/Nimbus/main.swift",
    "chars": 194,
    "preview": "//\n//  main.swift\n//  Nimbus\n//\n//  Created by Ethan Lowman on 7/25/14.\n//  Copyright (c) 2014 Ethanal. All rights reser"
  },
  {
    "path": "NimbusMenuBarApp/Nimbus/Nimbus.xcodeproj/project.pbxproj",
    "chars": 26313,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "NimbusMenuBarApp/Nimbus/Nimbus.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "chars": 151,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:Nimbus.xcodepro"
  },
  {
    "path": "NimbusMenuBarApp/Nimbus/Nimbus.xcodeproj/xcshareddata/xcschemes/Nimbus.xcscheme",
    "chars": 3867,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"0720\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "NimbusMenuBarApp/Nimbus/NimbusTests/Info.plist",
    "chars": 733,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "NimbusMenuBarApp/Nimbus/NimbusTests/NimbusTests.swift",
    "chars": 894,
    "preview": "//\n//  NimbusTests.swift\n//  NimbusTests\n//\n//  Created by Ethan Lowman on 7/25/14.\n//  Copyright (c) 2014 Ethanal. All "
  },
  {
    "path": "README.md",
    "chars": 5931,
    "preview": "<h1 align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/ethanal/Nimbus/master/graphics_assets/exports/login_lo"
  },
  {
    "path": "api_docs.md",
    "chars": 4770,
    "preview": "# API Reference\n\nThe Nimbus API uses token authentication, so it is recommended that you secure the `account` and `api` "
  },
  {
    "path": "graphics_assets/assets.sketch/metadata",
    "chars": 487,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "graphics_assets/assets.sketch/version",
    "chars": 2,
    "preview": "37"
  },
  {
    "path": "manage.py",
    "chars": 250,
    "preview": "#!/usr/bin/env python\nimport os\nimport sys\n\n\nif __name__ == \"__main__\":\n    os.environ.setdefault(\"DJANGO_SETTINGS_MODUL"
  },
  {
    "path": "nimbus/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "nimbus/apps/__init__.py",
    "chars": 280,
    "preview": "from django.conf.urls import patterns, include, url\nfrom nimbus import settings\n\n\ndef debug_urls():\n    if settings.SHOW"
  },
  {
    "path": "nimbus/apps/accounts/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "nimbus/apps/accounts/forms.py",
    "chars": 1075,
    "preview": "import logging\nfrom django.contrib.auth.forms import AuthenticationForm\nfrom django import forms\nfrom django.utils.html "
  },
  {
    "path": "nimbus/apps/accounts/urls.py",
    "chars": 543,
    "preview": "from django.conf.urls import include, url\nfrom django.contrib import admin\nfrom . import views\nfrom nimbus.apps import d"
  },
  {
    "path": "nimbus/apps/accounts/views.py",
    "chars": 2461,
    "preview": "import logging\nfrom .forms import AuthenticateForm\nfrom django.contrib.auth.decorators import login_required\nfrom django"
  },
  {
    "path": "nimbus/apps/api/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "nimbus/apps/api/urls.py",
    "chars": 722,
    "preview": "from django.conf.urls import url, include\nfrom nimbus.apps import debug_urls\nfrom . import views\n\n\nurlpatterns = debug_u"
  },
  {
    "path": "nimbus/apps/api/views.py",
    "chars": 3130,
    "preview": "from django.shortcuts import get_object_or_404\nfrom django.template.loader import render_to_string\nfrom rest_framework i"
  },
  {
    "path": "nimbus/apps/media/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "nimbus/apps/media/admin.py",
    "chars": 926,
    "preview": "from django.contrib import admin\nfrom .models import Media\n\n\n\nclass MediaAdmin(admin.ModelAdmin):\n    fields = (\"url_has"
  },
  {
    "path": "nimbus/apps/media/models.py",
    "chars": 5590,
    "preview": "import mimetypes\nimport uuid\nimport string\nimport random\nfrom pygments import highlight\nfrom pygments.lexers import gues"
  },
  {
    "path": "nimbus/apps/media/serializers.py",
    "chars": 2213,
    "preview": "from rest_framework import serializers\nfrom subdomains.utils import reverse\nfrom nimbus.apps.media.models import Media\n\n"
  },
  {
    "path": "nimbus/apps/media/urls.py",
    "chars": 395,
    "preview": "from django.conf.urls import url\nfrom django.views.generic.base import RedirectView\nfrom subdomains.utils import reverse"
  },
  {
    "path": "nimbus/apps/media/views.py",
    "chars": 842,
    "preview": "from pygments.formatters import HtmlFormatter\nfrom django.shortcuts import render, get_object_or_404, redirect\nfrom .mod"
  },
  {
    "path": "nimbus/settings/__init__.py",
    "chars": 128,
    "preview": "import os\nimport sys\n\nif os.getenv(\"PRODUCTION\", \"FALSE\") == \"TRUE\":\n    from production import *\nelse:\n    from local i"
  },
  {
    "path": "nimbus/settings/base.py",
    "chars": 6345,
    "preview": "import os\nimport re\nfrom fnmatch import fnmatch\nfrom boto.s3.connection import VHostCallingFormat\nfrom secret import *\n\n"
  },
  {
    "path": "nimbus/settings/local.py",
    "chars": 1378,
    "preview": "import os\nimport logging\nfrom .base import *\n\nlogger = logging.getLogger(__name__)\n\nDEBUG = True\nTEMPLATE_DEBUG = DEBUG\n"
  },
  {
    "path": "nimbus/settings/production.py",
    "chars": 694,
    "preview": "import urlparse\nfrom .base import *\n\n\nDEBUG = os.getenv(\"DEBUG\", \"FALSE\") == \"TRUE\"\nTEMPLATE_DEBUG = DEBUG\n\nSHOW_DEBUG_T"
  },
  {
    "path": "nimbus/settings/secret.sample.py",
    "chars": 240,
    "preview": "\"\"\"\nRename this file to 'secret.py' once all settings are defined\n\n\"\"\"\n\nSECRET_KEY = \"...\"\n\nHOSTNAME = \"example.com\"\n\nDA"
  },
  {
    "path": "nimbus/static/css/base.css",
    "chars": 544,
    "preview": "#main-nav {\n    border-radius: 0;\n}\n#main-nav .navbar-brand {\n    padding: 7px 15px;\n}\n.cloud-logo {\n    vertical-align:"
  },
  {
    "path": "nimbus/static/css/dashboard.css",
    "chars": 1868,
    "preview": "body {\n    margin-bottom: 50px;\n}\n\n#media-type-selector a {\n    color: #777;\n}\n\n#media-type-selector li.active a {\n    b"
  },
  {
    "path": "nimbus/static/css/login.css",
    "chars": 206,
    "preview": "#login-heading {\n    margin-top: 40px;\n    text-align: center;\n}\n\n#cloud-logo {\n    margin: 20px;\n    width: 240px;\n}\n\nh"
  },
  {
    "path": "nimbus/static/css/share_preview.css",
    "chars": 657,
    "preview": ".wrapper {\n    margin: 30px auto;\n    padding: 10px;\n}\n\n#text-preview {\n    max-width: 780px;\n}\n\n#media-name {\n    posit"
  },
  {
    "path": "nimbus/static/js/dashboard.js",
    "chars": 4601,
    "preview": "$(function() {\n    var deleteCheckboxClicked = function() {\n        if ($(\".delete-checkbox\").filter(\":checked\").length "
  },
  {
    "path": "nimbus/static/js/jquery-photo-resize.js",
    "chars": 809,
    "preview": "(function ($) {\n\n    $.fn.photoResize = function (options) {\n\n        var element = $(this),\n            defaults = {\n  "
  },
  {
    "path": "nimbus/templates/admin/base_site.html",
    "chars": 220,
    "preview": "{% extends \"admin/base.html\" %}\n\n{% block title %}\n    Nimbus Admin - {{ title|escape }}\n{% endblock %}\n\n{% block brandi"
  },
  {
    "path": "nimbus/templates/nimbus/accounts/dashboard.html",
    "chars": 5930,
    "preview": "{% extends \"nimbus/page_base.html\" %}\n{% load staticfiles %}\n{% load subdomainurls %}\n\n{% block title %}\n    {{ block.su"
  },
  {
    "path": "nimbus/templates/nimbus/accounts/login.html",
    "chars": 2315,
    "preview": "{% extends \"nimbus/page_base.html\" %}\n{% load staticfiles %}\n{% load widget_tweaks %}\n\n{% block title %}\n    {{ block.su"
  },
  {
    "path": "nimbus/templates/nimbus/accounts/media_table_row.html",
    "chars": 602,
    "preview": "{% load subdomainurls %}\n\n<tr data-url_hash=\"{{ media_item.url_hash }}\">\n    <td>\n        <input type=\"checkbox\" class=\""
  },
  {
    "path": "nimbus/templates/nimbus/media/share_base.html",
    "chars": 1025,
    "preview": "{% extends \"nimbus/page_base.html\" %}\n{% load staticfiles %}\n\n{% block title %}{{ media_item.name }}{% endblock %}\n\n{% b"
  },
  {
    "path": "nimbus/templates/nimbus/media/share_download.html",
    "chars": 352,
    "preview": "{% extends \"nimbus/media/share_base.html\" %}\n\n{% block media_preview %}\n    <div id=\"file-download\" class=\"wrapper\">\n   "
  },
  {
    "path": "nimbus/templates/nimbus/media/share_img_preview.html",
    "chars": 537,
    "preview": "{% extends \"nimbus/media/share_base.html\" %}\n{% load staticfiles %}\n\n{% block js %}\n    {{ block.super }}\n    <script sr"
  },
  {
    "path": "nimbus/templates/nimbus/media/share_txt_preview.html",
    "chars": 4486,
    "preview": "{% extends \"nimbus/media/share_base.html\" %}\n\n{% block css %}\n    {{ block.super }}\n    <style>\n        .highlight .hll "
  },
  {
    "path": "nimbus/templates/nimbus/page_base.html",
    "chars": 835,
    "preview": "{% load staticfiles %}\n\n<!doctype html>\n<html lang='en'>\n<head>\n    <title>{% block title %}Nimbus{% endblock %}</title>"
  },
  {
    "path": "nimbus/templates/rest_framework/api.html",
    "chars": 186,
    "preview": "{% extends \"rest_framework/base.html\" %}\n\n{% block title %}\nNimbus API - {{request.path}}\n{% endblock %}\n\n {% block bran"
  },
  {
    "path": "nimbus/templates/rest_framework/login.html",
    "chars": 267,
    "preview": "{% extends \"rest_framework/login_base.html\" %}\n\n{% block branding %}\n    <h3 style=\"margin: 0 0 20px;\">Nimbus API</h3>\n{"
  },
  {
    "path": "nimbus/utils.py",
    "chars": 1222,
    "preview": "from corsheaders import defaults as settings\nfrom corsheaders.middleware import (\n    CorsPostCsrfMiddleware, CorsMiddle"
  },
  {
    "path": "nimbus/wsgi.py",
    "chars": 1241,
    "preview": "\"\"\"\nWSGI config for nimbus project.\n\nThis module contains the WSGI application used by Django's development server\nand a"
  },
  {
    "path": "requirements/base.txt",
    "chars": 229,
    "preview": "Django==1.6.5\nMarkdown==2.4.1\nPygments==1.6\nboto==2.31.1\n-e git+https://github.com/ottoyiu/django-cors-headers.git#egg=P"
  },
  {
    "path": "requirements/local.txt",
    "chars": 66,
    "preview": "-r base.txt\n\ndjango-debug-toolbar==1.2.1\ndjango-extensions==1.3.8\n"
  },
  {
    "path": "requirements/production.txt",
    "chars": 70,
    "preview": "-r base.txt\n\ngunicorn==19.0.0\nMySQL-python==1.2.5\nnewrelic==2.40.0.34\n"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the ethanal/Nimbus GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 70 files (222.7 KB), approximately 54.4k tokens, and a symbol index with 57 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!