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
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
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.