cluster = require('cluster')
if cluster.isMaster
cluster.fork()
cluster.on 'disconnect', (worker) ->
console.error('restarting!')
cluster.fork()
return
async = require('async')
crypto = require('crypto')
dirty = require('dirty')
Dropbox = require('dropbox')
fs = require('fs')
path = require('path')
readline = require("readline")
mkdirp = require('mkdirp')
gaze = require('gaze')
cursorId = '0d32db1a-17f2-4aa8-9060-2a9eb72ec355'
apiKey = process.env.DROPBOX_API_KEY
apiSecret = process.env.DROPBOX_API_SECRET
userToken = process.env.DROPBOX_USER_TOKEN
base = process.env.DROPBOX_LOCAL_DIR
pathPrefix = process.env.DROPBOX_PATH_PREFIX
if base
if base[0] == '/'
base = base.substring(1)
if pathPrefix
if pathPrefix[0] == '/'
pathPrefix = pathPrefix.substring(1)
hash = (data) ->
shasum = crypto.createHash('sha1')
shasum.update(data)
shasum.digest('hex')
unless fs.existsSync(base)
fs.mkdirSync(base)
unless fs.existsSync(process.env.HOME + '/.smc-dropbox/filecache.db')
unless fs.existsSync(process.env.HOME + '/.smc-dropbox')
fs.mkdirSync(process.env.HOME + '/.smc-dropbox')
fs.writeFileSync(process.env.HOME + '/.smc-dropbox/filecache.db', '')
filecache = dirty(process.env.HOME + '/.smc-dropbox/filecache.db')
params =
key : apiKey
secret : apiSecret
token : userToken? && userToken
client = new Dropbox.Client(params)
localToDropboxPath (path) ->
return '' unless path
if path[0] == '/'
path = path.substring(1)
if path.indexOf(pathPrefix) == 0
return path
if path.indexOf(base) == 0
return pathPrefix + path.substring(base.length())
else
return path
dropboxToLocalPath (path) ->
return '' unless path
if path[0] == '/'
path = path.substring(1)
if path.indexOf(base) == 0
return path
if path.indexOf(pathPrefix) == 0
return base + path.substring(pathPrefix.length())
else
return path
writeFile = (filepath, data, stat, cb) ->
if stat == null
stat = {}
console.log("writing file")
fs.exists filepath, (exists) ->
if exists
console.log("file exists", filepath, "... overwriting")
fs.writeFile filepath, data, (err) ->
if err
throw(err) unless cb?
cb(err)
console.log('file written')
stat.hash = hash(data)
filecache.set(filepath, stat)
cb() if cb?
onDropboxChanges = (db, delta, cb) ->
onDropboxChange = (change, cb) ->
console.log("Dropbox announced change for", change.path)
if change.wasRemoved
console.log("nuking file from dropbox change")
filepath = dropboxToLocalPath(change.path)
fs.exists filepath, (exists) ->
if exists
fs.stat filepath, (error, stats) ->
throw(error) if error
if stats.isFile()
console.log("removing file")
filecache.rm(filepath)
fs.unlink(filepath, cb)
else if stats.isDirectory()
console.log("removing directory")
filecache.rm(filepath)
fs.rmdir(filepath, cb)
else
console.log("Non-file non-directory is ignored")
cb()
else
filecache.rm(filepath)
cb()
return
console.log("change.stat.path", change.stat.path)
filepath = dropboxToLocalPath(change.stat.path)
cache = filecache.get(filepath)
if cache?.versionTag == change.stat.versionTag
console.log('ignoring because same version')
cb()
return
if change.stat.isFolder
console.log('adding folder')
mkdirp(filepath, cb)
return
db.readFile change.path, { buffer: true, rev: change.stat.versionTag }, (error, data, stat, rangeInfo) ->
if error
console.log(error)
process.exit(1)
if rangeInfo
console.log("RangeInfo not supported")
process.exit(1)
console.log("Read", filepath, data)
fs.exists path.dirname(filepath), (exists) ->
console.log(path.dirname(filepath), "exists")
if exists
writeFile(filepath, data, stat, cb)
else
mkdirp path.dirname(filepath), (error) ->
console.log("mkdir error", error)
writeFile(filepath, data, stat, cb)
changes = delta.changes.filter (change) ->
i = change.path.indexOf(pathPrefix)
(i == 0) || (i == 1)
async.each changes, onDropboxChange, (error) ->
if error
if cb?
cb(error)
else
throw(error)
console.log("Writing cursor", delta.cursor())
filecache.set(cursorId, delta.cursor())
cb() if cb?
onLocalFileChange = (db, event, filename) ->
console.log("Event", event, "on", filename)
filepath = base + '/' + filename
if event == 'deleted'
if filecache.get(filepath)?
console.log("nuking", filepath, "from dropbox")
db.delete localToDropboxPath(filename), (error) ->
throw(error) if error?
filecache.rm(filepath)
else
console.log("file already deleted")
else if event == 'changed' || event == 'added'
fs.stat filepath, (error, stats) ->
throw(error) if error
cache = filecache.get(filepath)
if stats.isDirectory()
unless cache?.isFolder
db.mkdir localToDropboxPath(filename), (error) ->
throw(error) if error
else
fs.readFile localToDropboxPath(filepath), (error, data) ->
if error
console.log("Error", error)
process.exit(1)
cache = filecache.get(base + '/' + filename)
datahash = hash(data)
if cache?.hash != datahash
console.log("Stale cache. Triggering Dropbox update", cache?.hash, datahash)
db.writeFile filename, data, (error, stat) ->
throw(error) if error
console.log("file written to dropbox")
stat.hash = datahash
filecache.set(filepath, stat)
else
console.log("File up-to-date!")
filecache.on 'load', ->
client.authenticate (error, client) ->
grabChanges = (cursor, cb) ->
client.pullChanges cursor, (error, delta) ->
if error
console.log(error)
process.exit(1)
onDropboxChanges(client, delta, cb)
if error
console.log(error)
process.exit(1)
poll = ->
callback = (wait) ->
(error) ->
if error
console.log("Error:", error)
console.log("will poll in", wait, "ms")
setTimeout(poll, wait)
cursor = filecache.get(cursorId)
cursor = null unless cursor?
if cursor?
client.pollForChanges cursor, (error, result) ->
console.log('poll for changes returned')
throw(error) if error
console.log(result)
wait = result.retryAfter * 1000
if result.hasChanges
grabChanges(cursor, callback(wait))
else
callback(wait)()
else
grabChanges(null, callback(0))
cwd = process.cwd()
console.log("gaze starting")
gaze base + '/**/*', (error, watcher) ->
throw error if error
watcher.on 'all', (event, filepath) ->
filepath = filepath.substring(cwd.length + 1 + base.length + 1)
onLocalFileChange(client, event, filepath)
console.log("polling")
poll()