forked from zuk/Backbone.Drowsy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
backbone.drowsy.coffee
327 lines (267 loc) · 10.1 KB
/
backbone.drowsy.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
if window?
# we're running in a browser
$ = window.$
_ = window._
Backbone = window.Backbone
# not sure why, but sometimes we get "Backbone has no method 'ajax'"
# errors unless we do this first
Backbone.ajax = $.ajax
else
# we're running in node
$ = require('jquery');
_ = require 'underscore'
Backbone = require 'backbone'
Backbone.$ = $
# for Drowsy.ObjectId
crypto = require 'crypto'
os = require 'os'
class Drowsy
# TODO: should machineid be tied to Drowsy.Server rather than the client?
@generateMongoObjectId: ->
(new Drowsy.ObjectId()).toString()
class Drowsy.Server
constructor: (url, options = {}) ->
if typeof url is 'object'
options = url
else if typeof url is 'string'
options.url = url
@options = options
url: ->
@options.url.replace(/\/$/,'') # remove trailing /
database: (dbName) =>
new Drowsy.Database(@, dbName)
databases: (after) =>
deferredFetch = $.Deferred()
Backbone.ajax
url: @url()
dataType: 'json'
success: (data) =>
dbs = []
for dbName in data
if dbName.match Drowsy.Database.VALID_DB_RX
dbs.push @database(dbName)
deferredFetch.resolve(dbs)
after(dbs) if after?
error: (xhr, status) =>
deferredFetch.reject(status, xhr)
return deferredFetch
createDatabase: (dbName, after) =>
deferredCreate = $.Deferred()
Backbone.ajax
url: @url()
type: 'POST'
data: {db: dbName}
.done (data, status, xhr) =>
if status is 'success'
deferredCreate.resolve('already_exists', xhr)
after('already_exists') if after?
else
deferredCreate.resolve(status, xhr)
after(status) if after?
.fail (xhr, status) =>
if xhr.status is 0 and xhr.responseText is ""
# CORS requests come through as
deferredCreate.resolve('cors_mystery')
deferredCreate.reject(xhr)
after('failed') if after?
return deferredCreate
class Drowsy.Database # this should be anonymous, but we're naming it for clarity in debugging
@VALID_DB_RX: /[^\s\.\$\/\\\*]+/
constructor: (server, dbName, options = {}) ->
if typeof server is 'string'
server = new Drowsy.Server(server)
@server = server
@name = dbName
@options = options
@url = server.url() + '/' + dbName
collections: (after) =>
deferredFetch = $.Deferred()
Backbone.ajax
url: @url
dataType: 'json'
.done (data, status, xhr) =>
colls = []
for collName in data
c = new class extends @Collection(collName)
colls.push c
deferredFetch.resolve(colls)
after(colls) if after?
.fail (xhr, status) =>
deferredFetch.reject(xhr)
after('failed') if after?
return deferredFetch
createCollection: (collectionName, after) =>
deferredCreate = $.Deferred()
Backbone.ajax
url: @url
type: 'POST'
data: {collection: collectionName}
.done (data, status, xhr) =>
if status is 'success'
deferredCreate.resolve('already_exists', xhr)
after('already_exists') if after?
else
deferredCreate.resolve(status, xhr)
after(status) if after?
.fail (xhr, status) =>
deferredCreate.reject(xhr)
after('failed') if after?
return deferredCreate
Document: (collectionName) =>
db = @
class extends Drowsy.Document # this should be anonymous, but we're naming it for clarity in debugging
urlRoot: db.url + '/' + collectionName
collectionName: collectionName
Collection: (collectionName) =>
db = @
class extends Drowsy.Collection # this should be anonymous, but we're naming it for clarity in debugging
url: db.url + '/' + collectionName
name: collectionName
class Drowsy.Collection extends Backbone.Collection
model: Drowsy.Document
class Drowsy.Document extends Backbone.Model
idAttribute: '_id'
initialize: ->
@set @idAttribute, Drowsy.generateMongoObjectId() unless @has(@idAttribute)
set: (key, val, options) ->
res = super(key, val, options)
@dirty ?= {}
_.extend(@dirty, @changedAttributes())
return res
sync: (method, model, options) ->
res = super(method, model, options)
@dirty = {}
return res
reset: ->
res = super()
@dirty = {}
return res
fetch: (options = {}) ->
# FIXME: couldn't think of a better way to do this... couldn't use deferred because
# .done() fires after 'sync' is triggered so it's too late
# ... also should we do this on 'always' or just 'success'?
originalSuccess = options.success
options.success = (doc, data, xhr) =>
originalSuccess(doc,data,xhr) if originalSuccess?
@dirty = {}
res = super(options)
return res
dirtyAttributes: ->
@dirty
parse: (data) ->
data._id = data._id.$oid ? data._id
# convert all { $data: "..." } to Date() object
parsed = @parseObjectRecursively data, @jsonToDate
parsed
toJSON: (options = {}) ->
data = super(options)
parsed = @parseObjectRecursively data, @dateToJson
parsed
###
private
###
# recursively parses all values in the object using the given parser
parseObjectRecursively: (obj, parser) ->
return null if obj is null
out = parser obj
# now see if we need recursive processing in case the parser didn't deal with it
if _.isArray out
for item,i in out
out[i] = @parseObjectRecursively out[i], parser
else if _.isObject(out) and
# check that this is an object that can be iterated over (as opposed to something like a Date)
Object.keys(out).length > 0
for key,val of out
out[key] = @parseObjectRecursively val, parser
return out
jsonToDate: (val) ->
if val? and val.$date?
date = new Date(val.$date)
if isNaN date.getTime()
val.$invalid = true
val
else
date
else
val
dateToJson: (val) ->
if val instanceof Date
{ "$date": val.toJSON() }
else
val
#
# Adapted from https://github.com/justaprogrammer/ObjectId.js
#
#*
#* Copyright (c) 2011 Justin Dearing ([email protected])
#* Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
#* and GPL (http://www.opensource.org/licenses/gpl-license.php) version 2 licenses.
#* This software is not distributed under version 3 or later of the GPL.
#*
#* Version 1.0.0
#*
#
###
Javascript class that mimics how WCF serializes a object of type MongoDB.Bson.ObjectId
and converts between that format and the standard 24 character representation.
TODO: move this stuff out into its own module...
###
class Drowsy.ObjectId
@increment: 0
constructor: (oid, machine, pid, incr) ->
pid = pid ? Math.floor(Math.random() * (32767))
machine = machine ? Math.floor(Math.random() * (16777216))
if document?
if localStorage?
mongoMachineId = parseInt(localStorage["mongoMachineId"])
machine = Math.floor(localStorage["mongoMachineId"]) if mongoMachineId >= 0 and mongoMachineId <= 16777215
# Just always stick the value in.
localStorage["mongoMachineId"] = machine
document.cookie = "mongoMachineId=" + machine + ";expires=Tue, 19 Jan 2038 05:00:00 GMT"
else
cookieList = document.cookie.split("; ")
for i of cookieList
cookie = cookieList[i].split("=")
if cookie[0] is "mongoMachineId" and cookie[1] >= 0 and cookie[1] <= 16777215
machine = cookie[1]
break
document.cookie = "mongoMachineId=" + machine + ";expires=Tue, 19 Jan 2038 05:00:00 GMT"
else
mongoMachineId = crypto.createHash('md5').update(os.hostname()).digest('binary')
if typeof oid is "object"
@timestamp = oid.timestamp
@machine = oid.machine
@pid = oid.pid
@increment = oid.increment
else if typeof oid is "string" and oid.length is 24
@timestamp = Number("0x" + oid.substr(0, 8))
@machine = Number("0x" + oid.substr(8, 6))
@pid = Number("0x" + oid.substr(14, 4))
@increment = Number("0x" + oid.substr(18, 6))
else if oid? and machine? and pid? and incr?
@timestamp = oid
@machine = machine
@pid = pid
@increment = incr
else
@timestamp = Math.floor(new Date().valueOf() / 1000)
@machine = machine
@pid = pid
Drowsy.ObjectId.increment = 0 if Drowsy.ObjectId.increment > 0xffffff
@increment = Drowsy.ObjectId.increment++
getDate: ->
new Date(@timestamp * 1000)
###
Turns a WCF representation of a BSON ObjectId into a 24 character string representation.
###
toString: ->
timestamp = @timestamp.toString(16)
machine = @machine.toString(16)
pid = @pid.toString(16)
increment = @increment.toString(16)
return "00000000".substr(0, 6 - timestamp.length) + timestamp +
"000000".substr(0, 6 - machine.length) + machine +
"0000".substr(0, 4 - pid.length) + pid +
"000000".substr(0, 6 - increment.length) + increment
root = exports ? this
root.Drowsy = Drowsy